并发二

网友投稿 604 2022-10-22

并发二

并发二

一、volatile 原理

线程频繁对主内存读写操作,会产生性能问题,因此线程都有一个自己的工作内存。读写操作都会在工作内存中执行,而不会到主内存中。

volatile 的底层实现原理是内存屏障:

对 volatile 变量的写指令后会加入写屏障对 volatile 变量的读指令前会加入读屏障

1.1 保证可见性

写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中

public void actor2(I_Result r) { num = 2; ready = true; // ready是被volatile修饰的 ,赋值带写屏障 // 写屏障 }

而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据

1. public void actor1(I_Result r) {// 读屏障// ready是被volatile修饰的 ,读取值带读屏障if(ready) {r.r1 = num + num;} else {r.r1 = 1;}}

1.2保证有序性

写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后

public void actor2(I_Result r) {num = 2;ready = true; // ready是被volatile修饰的 , 赋值带写屏障// 写屏障}

读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前 ​x 1public void actor1(I_Result r) {2// 读屏障3// ready是被volatile修饰的 ,读取值带读屏障4if(ready) {5r.r1 = num + num;6} else {7r.r1 = 1;8}9}​而有序性的保证也只是保证了本线程内相关代码不被重排序

一句话:volatile 能保证可见性和有序性,但无法保证原子性。monitor则可以同时保证这三个

以上的实现特点是: 0. 懒惰实例化1. 首次使用 getInstance() 才使用 synchronized 加锁,后续使用时无需加锁2. 有隐含的,但很关键的一点:第一个 if 使用了 INSTANCE 变量,是在同步块之外 但在多线程环境下,上面的代码是有问题的,getInstance 方法对应的字节码为: ```0: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;3: ifnonnull 37// ldc是获得类对象6: ldc #3 // class cn/itcast/n5/Singleton// 复制操作数栈栈顶的值放入栈顶, 将类对象的引用地址复制了一份8: dup// 操作数栈栈顶的值弹出,即将对象的引用地址存到局部变量表中// 将类对象的引用地址存储了一份,是为了将来解锁用9: astore_010: monitorenter11: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;14: ifnonnull 27// 新建一个实例17: new #3 // class cn/itcast/n5/Singleton// 复制了一个实例的引用20: dup// 通过这个复制的引用调用它的构造方法21: invokespecial #4 // Method :()V// 最开始的这个引用用来进行赋值操作24: putstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;27: aload_028: monitorexit29: goto 3732: astore_133: aload_034: monitorexit35: aload_136: athrow37: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;40: areturn

1.3 happens-before规则

下面说的变量都是指成员变量或静态成员变量

1)线程解锁 m 之前对变量的写,对于接下来对 m 加锁的其它线程对该变量的读可见

static int x; static Object m = new Object(); new Thread(()->{ synchronized(m) { x = 10; } },t1).start(); new Thread(()->{ synchronized(m) { System.out.println(x); } },t2).start();

2)线程对 volatile 变量的写,对接下来其它线程对该变量的读可见

volatile static int x; new Thread(()->{ x = 10; },t1).start(); new Thread(()->{ System.out.println(x); },t2).start();

3)线程 start 前对变量的写,对该线程开始后对该变量的读可见

static int x; x = 10; new Thread(()->{ System.out.println(x); },t2).start();

4)线程结束前对变量的写,对其它线程得知它结束后的读可见(比如其它线程调用 t1.isAlive() 或 t1.join()等待它结束)

static int x;Thread t1 = new Thread(()->{ x = 10;},t1);t1.start();t1.join();System.out.println(x);

5)线程 t1 打断 t2(interrupt)前对变量的写,对于其他线程得知 t2 被打断后对变量的读可见(通过 t2.interrupted 或 t2.isInterrupted)

static int x; public static void main(String[] args) { Thread t2 = new Thread(()->{ while(true) { if(Thread.currentThread().isInterrupted()) { System.out.println(x); break; } } },t2); t2.start(); new Thread(()->{ sleep(1); x = 10; t2.interrupt(); },t1).start(); while(!t2.isInterrupted()) { Thread.yield(); } System.out.println(x); }

6)对变量默认值(0,false,null)的写,对其它线程对该变量的读可见

7)具有传递性,如果 x hb-> y 并且 y hb-> z 那么有 x hb-> z ,配合 volatile 的防指令重排,有下面的例子

volatile static int x; static int y; new Thread(()->{ y = 10; x = 20; },t1).start(); new Thread(()->{ // x=20 对 t2 可见, 同时 y=10 也对 t2 可见 System.out.println(x); },t2).start();

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:AndNet- Android二次封装的网络框架
下一篇:使用Mybatis如何实现删除多个数据
相关文章

 发表评论

暂时没有评论,来抢沙发吧~