并发编程(一)可见性
创始人
2024-02-01 17:11: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关键字,会让所有线程都会读到变量的修改值* 

* 在下面的代码中,running是存在于堆内存的t对象中* 当线程t1开始运行的时候,会把running值从内存中读到t1线程的工作区,在运行过程中直接使用这个copy,并不会每次都去* 读取堆内存,这样,当主线程修改running的值之后,t1线程感知不到,所以不会停止运行*

* 使用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; //8bytesprivate 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 {@Contendedpublic 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除了保障线程可见性之外 , 它还可以禁止重排序。了解完有序性之后才能真正理解它的底层实现原理。

相关内容

热门资讯

应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...