并发编程(一)可见性

news2025/4/16 13:59:15

【并发编程三大特性】:

可见性
有序性
原子性( 较复杂 )

【线程的可见性】:

【一个例子认识线程的可见性】:

import Utils.SleepHelper;
import java.io.IOException;

public class T01_HelloVolatile {
    private static  boolean running = true;  //位于主内存当中。

    private static void m() {
        System.out.println("m start");
        while (running) {
        	//程序最终将陷在这里,一直进入死循环。
        }
        System.out.println("m end!");
    }

    public static void main(String[] args) throws IOException {

        new Thread(T01_HelloVolatile::m, "t1").start();

        SleepHelper.sleepSeconds(1);

        running = false;
    }
}

【最终输出】:
在这里插入图片描述
//发现程序停下了,一直在运行并没有结束。

【 并不会每次都去读去堆内存 】:

running位于主存当中,T1线程只去读取一次;
在上面的代码中,running是存在于堆内存的t对象中

  • 当线程t1开始运行的时候,会把running值从内存中读到t1线程的工作区,在运行过程中直接使用这个copy,并不会每次都去
  • 读取堆内存,这样,当主线程修改running的值之后,t1线程感知不到,所以不会停止运行
  • 使用volatile,将会强制所有线程都去堆内存中读取running的值
  • volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized

【引入Volatile关键字】:

/**
 * volatile 关键字,使一个变量在多个线程间可见
 * A B线程都用到一个变量,java默认是A线程中保留一份copy,这样如果B线程修改了该变量,则A线程未必知道
 * 使用volatile关键字,会让所有线程都会读到变量的修改值
 * <p>
 * 在下面的代码中,running是存在于堆内存的t对象中
 * 当线程t1开始运行的时候,会把running值从内存中读到t1线程的工作区,在运行过程中直接使用这个copy,并不会每次都去
 * 读取堆内存,这样,当主线程修改running的值之后,t1线程感知不到,所以不会停止运行
 * <p>
 * 使用volatile,将会强制所有线程都去堆内存中读取running的值
 * volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized
 */
package T02_Thread_Volatile;

import Utils.SleepHelper;

import java.io.IOException;

public class T01_HelloVolatile2 {
    private static volatile boolean running = true;  //位于主内存当中。

    private static void m() {
        System.out.println("m start");
        while (running) {
            //System.out.println("hello");
        }
        System.out.println("m end!");
    }

    public static void main(String[] args) throws IOException {

        new Thread(T01_HelloVolatile2::m, "t1").start();

        SleepHelper.sleepSeconds(1);

        running = false;

    }
}

【最终输出】:
在这里插入图片描述

【理解】:

volatile修饰的那块内存,对于它的任何修改,其他的线程立马可见。——保持可见性。

【线程间好像又可见了】:

import Utils.SleepHelper;
import java.io.IOException;

public class T01_HelloVolatile3 {
    private static  boolean running = true;  //位于主内存当中。

    private static void m() {
        System.out.println("m start");
        while (running) {
            System.out.println("hello");     //这里会触发可见性机制!!!!!!!!!!!
        }
        System.out.println("m end!");
    }

    public static void main(String[] args) throws IOException {

        new Thread(T01_HelloVolatile3::m, "t1").start();

        SleepHelper.sleepSeconds(1);

        running = false;
    }
}

【最终输出】:
在这里插入图片描述
//在输出若干个hello之后 , 线程结束了。

【触发了可见性机制】:

在这里插入图片描述
//synchronized也是可以保持可见性的。
【简单理解】:
在某些语句执行的情况下,它是可以触发本地的缓存和我们主内存之间的数据进行一个刷新和同步。

【volatile修饰引用类型】:

volatile 引用类型(包括数组)只能保证引用本身的可见性,不能保证内部字段的可见性。
在这里插入图片描述
//内存中有一个A对象 , 小a指向它 , 这个a是被volatile修饰的 , 两个线程本地的缓存里也都有一个 r , 一个线程修改自己内部的缓存r , 其他线程并不会可见 。
但是,如果小a指向的内容被修改了,其他线程就可见了.
在这里插入图片描述

[测试程序]:

public class T02_VolatileReference {

    private static class A {
        boolean running = true;   //要加volatile的话加在这里 ~

        void m() {
            System.out.println("m start");
            while (running) {
            }
            System.out.println("m end!");
        }
    }

    private volatile static A a = new A();

    public static void main(String[] args) {
        new Thread(a::m, "t1").start();
        SleepHelper.sleepSeconds(1);
        a.running = false;
    }
}

【输出】:
运行程序,发现一直在循环。
在这里插入图片描述

【改进程序】:

public class T02_VolatileReference2 {

    private static class A {
       volatile boolean running = true;
        void m() {
            System.out.println("m start");
            while (running) {
            }
            System.out.println("m end!");
        }
    }

    private volatile static A a = new A();

    public static void main(String[] args) {
        new Thread(a::m, "t1").start();
        SleepHelper.sleepSeconds(1);
        a.running = false;
    }
}

【最终输出】:
在这里插入图片描述

【三级缓存】:

Registers -> L1 -> L2 -> L3 -> 内存。
寄存器需要一个数据首先会去L1里找,然后依次去L2、L3、内存中找。回去的时候先往L3中存一份,再往L2中存2一份,再往L1中存一份,最后存到寄存器里。我们说的可见性并不是ThreadLocal , 而是各个CPU中L123的数据的可见性。
在这里插入图片描述
在这里插入图片描述

【缓存行的基本概念】:

【读数据时的效率】:

并非每次需要变量都是从内存中读取数据都是L3、2、1这种,而是一整块儿一整块儿地从内存中读出来。
【程序的局部性原理】:
按块读取;
程序局部性原理 , 可以提高效率;
充分发挥总线CPU针脚等一次性读取更多数据的能力。
【空间局部性原理】:
当我用到某一个值的时候,我很快就会用到和它相邻的值。用到这个值的时候,我一次性把它周边的值都给读取到缓存里,并非只读一个。
在这里插入图片描述

【时间局部性原理】:
当我用到一个指令的时候 , 很快我会用到和它相邻的指令,也是一次性地将很多指令相关的数据全给读取到内存里面;

【这一块儿数据到底有多大呢?】:

专业名词——CacheLine , 一行大小64bytes 。
在这里插入图片描述

【 通过程序认识缓存一致性 】:

public class T01_CacheLinePadding {
    public static long COUNT = 10_0000_0000L;

    private static class T {
        public long x = 0L; //8 bytes  ——————8个Long类型就能够把缓存行给填满。
    }

    public static T[] arr = new T[2];

    static {
        arr[0] = new T();
        arr[1] = new T();
    }

    public static void main(String[] args) throws Exception {
        CountDownLatch latch = new CountDownLatch(2);

        Thread t1 = new Thread(() -> {
            for (long i = 0; i < COUNT; i++) {
                arr[0].x = i;
            }

            latch.countDown();
        });

        Thread t2 = new Thread(() -> {
            for (long i = 0; i < COUNT; i++) {
                arr[1].x = i;
            }

            latch.countDown();
        });

        final long start = System.nanoTime();
        t1.start();
        t2.start();
        latch.await();
        System.out.println((System.nanoTime() - start) / 100_0000);
    }
}

【最终输出】:
在这里插入图片描述
//运行了很多次——几乎都在600左右~~~。

【修改程序】:

在上述程序的基础上只修改一处:

    private static class T {
        private long p1, p2, p3, p4, p5, p6, p7;
        public long x = 0L; //8bytes
        private long p9, p10, p11, p12, p13, p14, p15;
    }

【最终发现】:
程序运行变快了——到了300多秒!!!!!!

//跟volatile没有关系 , 你加不加volatile都能够体现出效率的区别来。
在这里插入图片描述
//这是由于缓存一致性协议所导致的。
在这里插入图片描述
//多线程修改的内容位于同一缓存行的情况时会互相干扰。CPU1中的缓存行修改了,会去通知CPU2 ,同样CPU2中缓存行修改了会去通知CPU1。
在这里插入图片描述
前面加7个变量也就是56字节,56+8=64 , 所以任意两个变量绝对不会位于同一个缓存行当中。————互相之间不用做通知了,互相之间也不用同步了。

【 Disruptor闪电框架源码 】:

在这里插入图片描述
//最后面堆了7个Long就是为了利用缓存行机制。

【 认识Contended 】:

认识一个注解——Contended 。
被这个注解所标注的数据不会和其他数据位于同一缓存行。
【先不加注解】:

 import sun.misc.Contended;
//注意:运行这个小程序的时候,需要加参数:-XX:-RestrictContended
import java.util.concurrent.CountDownLatch;

public class T05_Contended {
    public static long COUNT = 10_0000_0000L;

    //@Contended  //只有1.8起作用 , 保证x位于单独一行中
    private static class T {
        public long x = 0L;
    }

    public static T[] arr = new T[2];

    static {
        arr[0] = new T();
        arr[1] = new T();
    }

    public static void main(String[] args) throws Exception {
        CountDownLatch latch = new CountDownLatch(2);

        Thread t1 = new Thread(() -> {
            for (long i = 0; i < COUNT; i++) {
                arr[0].x = i;
            }

            latch.countDown();
        });

        Thread t2 = new Thread(() -> {
            for (long i = 0; i < COUNT; i++) {
                arr[1].x = i;
            }

            latch.countDown();
        });

        final long start = System.nanoTime();
        t1.start();
        t2.start();
        latch.await();
        System.out.println((System.nanoTime() - start) / 100_0000);
    }
}

【再加上注解比较两者的速度】:


import sun.misc.Contended;
//注意:运行这个小程序的时候,需要加参数:-XX:-RestrictContended
import java.util.concurrent.CountDownLatch;

public class T05_Contended2 {
    public static long COUNT = 10_0000_0000L;

    //@Contended  //只有1.8起作用 , 保证x位于单独一行中
    private static class T {
        @Contended
        public long x = 0L;
    }

    public static T[] arr = new T[2];

    static {
        arr[0] = new T();
        arr[1] = new T();
    }

    public static void main(String[] args) throws Exception {
        CountDownLatch latch = new CountDownLatch(2);

        Thread t1 = new Thread(() -> {
            for (long i = 0; i < COUNT; i++) {
                arr[0].x = i;
            }

            latch.countDown();
        });

        Thread t2 = new Thread(() -> {
            for (long i = 0; i < COUNT; i++) {
                arr[1].x = i;
            }

            latch.countDown();
        });

        final long start = System.nanoTime();
        t1.start();
        t2.start();
        latch.await();
        System.out.println((System.nanoTime() - start) / 100_0000);
    }
}

//可以发现效率会大幅度地提升。
需要加上如下参数:
在这里插入图片描述
//默认情况下Contended的注解是被限制住的。你必须把这个参数打开,否则加了也不会起作用。

【 硬件层面的缓存一致性 】:

缓存一致性不要和MESI概念混淆。
在这里插入图片描述
//不同类型的CPU所采用的缓存一致性协议是不一样的。
MESI只是缓存一致性协议中的一种,这个协议是Intel公司设计的。MESI指的是缓存行的四种状态。
Modified——-被修改了;
Exclusive——独享;
Shared———共享;
Invalid———-失效;

Volatile底层并不是由MESI协议实现的!!!

【为什么缓存行一行64字节?】:

//工业实践当中得出的最佳实践。
缓存行越大 , 局部性空间效率越高 , 但读取时间慢。(缓存行大了的话,读取的时间就慢了。)
缓存行越小, 局部性空间效率越低 , 但读取时间快。
取一个折中值,目前多用:————64字节。

【总结】:

  • volatile保障线程可见性
  • 缓存行
  • 缓存一致性协议

【volatile的底层实现】:
volatile除了保障线程可见性之外 , 它还可以禁止重排序。了解完有序性之后才能真正理解它的底层实现原理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/18894.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Go中的泛型和反射以及序列化

嗨喽,小伙伴们,好几没有更新了,最近在搞一些云原生的东西,docker , k8s 搞得我暂时迷失了方向,不过我举得搞IT吗,就是在不断尝试,搞一下当下最新的技术,不然 … GO中的泛型与继承 搞过java的都知道泛型与继承,在go中也开始搞泛型与继承了(在go1.8之后) 先看代码–>> p…

【记录】PyCharm 安装 preprocess 模块(库)|| 在 PyCharm 中安装 preprocess 失败,故而在 终端 安装

preprocess.py 针对的是处理许多 文件类型。它工作的语言包括&#xff1a;C、Python、 perl、tcl、xml、javascript、css、idl、tex、fortran、php、java、shell 脚本&#xff08;bash、csh等&#xff09;和c。预处理可以作为 命令行应用程序和作为python 模块。 目录一、在 Py…

矩阵论复习提纲

矩阵论复习提纲 第一章 矩阵相似变化 1、特征值与特征向量 A ∈ Cnxn 若存在 λ ∈ C 满足 Ax λx 则 λ 为 A 的特征值 可转换为 &#xff08;λI - A&#xff09;x 0 特征多项式 &#xff1a;det(λI - A) 特征矩阵&#xff1a; λI - A 2、相似对角化 1. 判断可对角化…

VMware Fusion 13 正式版终于来了

千呼万唤&#xff0c;经历两年之久&#xff0c;VMware终于在Fusion 13正式版中支持了Apple Silicon 版Mac&#xff0c;此次发布的Fusion是Universal版本&#xff0c;也就是一个安装包同时适配Intel Mac及Apple Silicon &#xff08;M1&#xff0c;M2&#xff09;Mac。想起我两年…

疑难杂症集合(备忘)

sshd&#xff1a;no hostkeys available 解决过程: #ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key #ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key #/usr/sbin/sshd 如果上述两个文件存在&#xff0c;仍然出现这个错误&#xff0c;那么试试 chmod 600 上述两个文件。之…

01-Python的基本概念

01-Python的基本概念 Python是一种直译式&#xff08;Interpreted&#xff09;、面向对象&#xff08;Object Oriented&#xff09;的程序语言’它拥有完整的函数库’可以协助轻松地完成许多常见的工作。 所谓的直译式语言是指’直译器&#xff08;InteIpretor&#xff09;会将…

诊断故障码(Diagnostic Trouble Code-DTC)

诊断协议那些事儿 诊断协议那些事儿专栏系列文章&#xff0c;本文将由浅入深的介绍DTC&#xff08;Diagnostic Trouble Code&#xff09;。 关联文章&#xff1a; $19服务:DTCStatusMask和statusofDTC bit 定义 19服务List 文章目录诊断协议那些事儿DTC&#xff08;Diagnos…

Python pyenv install 下载安装慢(失败)完美解决

pyenv 下载原理就是将例如 Python-3.10.3.tar.xz 这样的压缩文件下载到本地缓存文件或临时文件&#xff0c;然后解压出来使用。 由于下载速度或者网不行&#xff0c;那么就跳过 pyenv 下载&#xff0c;自己下载&#xff0c;然后放到它的缓存文件&#xff0c;这样不就行了。 1…

3.np.random

1. np.random.seed np.random.seed()函数用于生成指定随机数。 seed()被设置了之后&#xff0c;np.random.random()可以按顺序产生一组固定的数组&#xff0c;如果使用相同的seed()值&#xff0c;则每次生成的随机数都相同。 如果不设置这个值&#xff0c;那么每次生成的随机数…

[附源码]计算机毕业设计JAVA基于Java的护肤品网站

[附源码]计算机毕业设计JAVA基于Java的护肤品网站 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM my…

【MySQL进阶】表的增删改查操作(CRUD)+(SQL执行顺序)

1. 新增(复制数据)2. 查询 - 进阶2.1 聚合查询2.2 group by2.3 having2.4 联合查询2.4.1 内连接2.4.2 外连接2.4.3 自连接2.4.4 子查询2.4.5 合并查询3 SQL的执行顺序(where...)1. 新增(复制数据) 语法 -- 字段名 列名 -- 将表2的数据复制到表1中 -- 两张表的结构要一样 i…

前端知识点

1.HTML 2.CSS 3.js 4.VUE 5.vUE的基本指令 6.VUE案例 7.ELEMENT HTML 设置图片 <img src"图片地址">让图片居中显示<center><img src"图片地址" width"270" height"900"></center>有序列表 <!--有序 ty…

Spring框架技术的核心与设计思想

目录 1. Spring 是什么? 1.1 什么是容器? 1.2 什么是 IoC ? 2. 传统式开发 3. 控制(权)反转式开发 4. 理解Spring 核心 - IoC 1. Spring 是什么? Spring 的全称是 Spring Framework, 它是一种开源框架, 2002 年, Rod Jahnson 首次推出了 Spring 框雏形 interface21…

[UE笔记]客户端服务器时间同步

内容系看教程所做的笔记 时间 往返时间&#xff08;RTT, Round-Trip Time&#xff09;&#xff1a;数据从客户端通过网络发送到服务器&#xff0c;再从服务器返回到客户端所需的时间。 首先客户端应当知道服务端的当前时间。 服务器启动时间总是先于客户端的&#xff0c;客户…

【学习OpenCV4】OpenCV入门精讲(C++/Python双语教学)

大家好&#xff0c;我在CSDN开的OpenCV课程已经开课了&#xff0c;入口如下&#xff1a; OpenCV入门精讲&#xff08;C/Python双语教学&#xff09; 课程开始快一周了&#xff0c;收到了广大同学们的欢迎。 &#xff08;1&#xff09;评分很高&#xff1b; &#xff08;2&#…

从Clickhouse 到 Snowflake: 云原生

云原生Clickhouse优势概述 以Clickhouse为基础&#xff0c;借鉴Snowflake等系统的设计思路&#xff0c;打造一款高性能的云原生OLAP系统&#xff0c;为用户提供多场景下的一站式的数据分析平台。 简单、易维护&#xff1a;集群管理、统一共享分布式调度服务高可用、可扩展&am…

T292114 [传智杯 #5 练习赛] 清洁工

题目描述 有一个 n\times nnn 的地块&#xff0c;一个连续 ii 分钟没人经过的地面在第 ii 分钟会落上 ii 个单位的灰&#xff0c;有人经过时不会落灰但灰也不会清零&#xff0c;在人走后第一分钟又会落上一个单位的灰&#xff0c;以此类推。你在这个 n\times nnn 的范围内移动…

深度可分离卷积神经网络与卷积神经网络

在学习语义分割过程中&#xff0c;接触到了深度可分离卷积神经网络&#xff0c;其是对卷积神经网络在运算速度上的改进&#xff0c;具体差别如下&#xff1a; 一些轻量级的网络&#xff0c;如mobilenet中&#xff0c;会有深度可分离卷积depthwise separable convolution&#…

Xamarin.Andorid实现界面弹框

目录1、使用系统自带的样式1.1 具体实现1.2 效果2、自定义样式的实现2.1 预期效果2.2 具体实现2.3 相关知识3 代码下载4、参考在App的实际使用中&#xff0c;一定会出现弹框选择的情况。如图所示&#xff1a; 因此非常有必须学会及使用弹框的功能&#xff0c;因此本次学习Xama…

miui刷机完整教程

风险提示&#xff1a;刷机有可能损害手机&#xff0c;本作者不承担因为使用本方法刷机引起的任何问题&#xff0c;请谨慎刷机。 1.选择[刷机包](https://web.vip.miui.com/page/info/mio/mio/detail?postId37093637&app_versiondev.20051) 2.解BL锁 浏览器打开http://www…