做网站价格差异很大,app大全免费软件下载安装,下面哪个是h5轻设计平台,泉州建行 网站文章目录 1.什么是可见性问题2.为什么会有可见性问题3.JMM的抽象#xff1a;主内存和本地内存3.1 什么是主内存和本地内存3.2 主内存和本地内存的关系 4.Happens-Before原则4.1 什么是Happens-Before4.2 什么不是Happens-Before4.3 Happens-Before规则有哪些4.4 演示#xff… 文章目录 1.什么是可见性问题2.为什么会有可见性问题3.JMM的抽象主内存和本地内存3.1 什么是主内存和本地内存3.2 主内存和本地内存的关系 4.Happens-Before原则4.1 什么是Happens-Before4.2 什么不是Happens-Before4.3 Happens-Before规则有哪些4.4 演示使用volatile修正可见性问题 5.volatile关键字5.1 volatile是什么5.2 volatile的适用场合5.3 volatile的两点作用5.4 volatile和synchronized的关系5.5 volatile小结 6.能保证可见性的措施7.升华对synchronized可见性的正确理解 1.什么是可见性问题
首先来看第一个代码案例演示什么是可见性问题。
/*** 演示可见性带来的问题*/
public class FieldVisibility {int a 1;int b 2;private void change() {a 3;b a;}private void print() {System.out.println(b b , a a);}public static void main(String[] args) {while (true) {FieldVisibility test new FieldVisibility();new Thread(new Runnable() {Overridepublic void run() {try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}test.change();}}).start();new Thread(new Runnable() {Overridepublic void run() {try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}test.print();}}).start();}}
}关于上述程序的运行结果我们可以很容易分析得到如下三种情况
b 2, a 3b 2, a 1b 3, a 3
然而在实际运行过程中还有可能会出现第四种情况概率低即 b 3, a 1。这是因为 a 虽然被修改了但是其他线程不可见而 b 恰好其他线程可见这就造成了 b 3, a 1。 2.为什么会有可见性问题
接下来尝试分析第二个案例。 至此解答一个问题为什么会有可见性问题
CPU有多级缓存导致读的数据过期。高速缓存的容量比主内存小但是速度仅次于寄存器所以在CPU和主内存之间就多了Cache层。线程间的对于共享变量的可见性问题不是直接由多核引起的而是由多缓存引起的。如果所有的核心都只用一个缓存那么也就不存在内存可见性问题了。每个核心都会将自己需要的数据读到独占缓存中数据修改后也是写入到缓存中然后等待刷入到主存中。所以会导致有些核心读取的值是一个过期的值。 3.JMM的抽象主内存和本地内存
3.1 什么是主内存和本地内存
Java作为高级语言屏蔽了这些底层细节用JMM定义了一套读写内存数据的规范虽然我们不再需要关心一级缓存和二级缓存的问题但是JMM抽象了主内存和本地内存的概念。
这里说的本地内存并不是真的是一块给每个线程分配的内存而是JMM的一个抽象是对于寄存器、一级缓存、二级缓存等的抽象。 3.2 主内存和本地内存的关系
JMM有以下规定
所有的变量都存储在主内存中同时每个线程也有自己独立的工作内存工作内存中的变量内容是主内存中的拷贝。线程不能直接读写主内存中的变量而是只能操作自己工作内存中的变量然后再同步到主内存中。主内存是多个线程共享的但线程间不共享工作内存如果线程间需要通信必须借助主内存中转来完成。
总结所有的共享变量存在于主内存中每个线程有自己的本地内存而且线程读写共享数据也是通过本地内存交换的所以才导致了可见性问题。
4.Happens-Before原则
4.1 什么是Happens-Before
下面的两种解释其实是一种意思。
Happens-Before规则是用来解决可见性问题的在时间上动作 A 发生在动作 B 之前B 保证能看见 A这就是Happens-Before。
两个操作可以用Happens-Before来确定它们的执行顺序如果一个操作Happens-Before于另一个操作那么我们说第一个操作对于第二个操作是可见的。
4.2 什么不是Happens-Before
两个线程没有相互配合的机制所以代码 X 和 Y 的执行结果并不能保证总被对方看到的这就不具备Happens-Before。
4.3 Happens-Before规则有哪些
(1) 单线程规则 (2) 锁操作synchronized和Look (3) volatile变量 (4) 线程启动 (5) 线程join (6) 传递性
传递性如果 hb(A,B) 而且 hb(B,C)那么可以推出 hb(A,C)。
(7) 中断
中断一个线程被其他线程 interrupt 时那么检测中断isInterrupted或者抛出 InterruptedException 一定能看到。
(8) 构造方法
构造方法对象构造方法的最后一行指令Happens-Before于 finalize() 方法的第一行指令。
(9) 工具类的Happens-Before原则
线程安全的容器get一定能看到在此之前的put等存入动作CountDownLatchSemaphoreFuture线程池CyclicBarrier
4.4 演示使用volatile修正可见性问题
Happens-Before有一个原则是如果 A 是对 volatile 变量的写操作B 是对同一个变量的读操作那么 hb(A,B)。
根据上面的原则可以使用 volatile 关键字解决本文开头第一个案例的可见性问题。
/*** 使用volatile关键字解决可见性问题*/public class FieldVisibility {int a 1;volatile int b 2; // 只给b加volatile即可// writerThreadprivate void change() {a 3;b a; // 作为刷新之前变量的触发器}// readerThreadprivate void print() {System.out.println(b b , a a);}public static void main(String[] args) {while (true) {FieldVisibility test new FieldVisibility();new Thread(new Runnable() {Overridepublic void run() {try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}test.change();}}).start();new Thread(new Runnable() {Overridepublic void run() {try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}test.print();}}).start();}}
}这里体现了 volatile 的一个很重要的功能近朱者赤。给 b 加了 volatile不仅 b 被影响也可以实现轻量级同步。
b 之前的写入对应代码ba对读取 b 后的代码print b都可见所以在 writerThread 里对 a 的赋值一定会对 readerThread 里的读取可见所以这里的 a 即使不加 volatile只要 b 读到是 3就可以由Happens-Before原则保证了读取到的都是 3 而不可能读取到 1。
5.volatile关键字
5.1 volatile是什么
volatile是一种同步机制比synchronized或者Lock相关类更轻量因为使用volatile并不会发生上下文切换等开销很大的行为。
如果一个变量被修饰成volatile那么JVM就知道了这个变量可能会被并发修改。
但是开销小相应的能力也小虽然说volatile是用来同步地保证线程安全的但是volatile做不到synchronized那样的原子保护volatile仅在很有限的场景下才能发挥作用。
5.2 volatile的适用场合
(1) 不适用于a
import java.util.concurrent.atomic.AtomicInteger;/*** volatile的不适用场景*/
public class NoVolatile implements Runnable {volatile int a;AtomicInteger realA new AtomicInteger();Overridepublic void run() {for (int i 0; i 10000; i) {a;realA.incrementAndGet();}}public static void main(String[] args) throws InterruptedException {Runnable r new NoVolatile();Thread thread1 new Thread(r);Thread thread2 new Thread(r);thread1.start();thread2.start();thread1.join();thread2.join();System.out.println(((NoVolatile) r).a);System.out.println(((NoVolatile) r).realA.get());}
}(2) 适用场景一
如果一个共享变量自始至终只被各个线程赋值而没有其他的操作那么就可以用volatile来代替synchronized或者代替原子变量因为赋值自身是有原子性的而volatile又保证了可见性所以就足以保证线程安全。例如boolean flag 操作。
注意volatile 适用的关键并不在于 boolean 类型而在于和之前的状态是否有关系。
在下面的程序中setDone() 的时候done 变量只是被赋值而没有其他的操作所以是线程安全的。
import java.util.concurrent.atomic.AtomicInteger;/*** volatile的适用场景*/
public class UseVolatile implements Runnable {volatile boolean done false;AtomicInteger realA new AtomicInteger();Overridepublic void run() {for (int i 0; i 10000; i) {setDone();realA.incrementAndGet();}}private void setDone() {done true;}public static void main(String[] args) throws InterruptedException {Runnable r new UseVolatile();Thread thread1 new Thread(r);Thread thread2 new Thread(r);thread1.start();thread2.start();thread1.join();thread2.join();System.out.println(((UseVolatile) r).done);System.out.println(((UseVolatile) r).realA.get());}
}在下面的程序中虽然 done 变量是 boolean 类型的但 flipDone() 的时候done 变量取决于之前的状态所以是线程不安全的。
import java.util.concurrent.atomic.AtomicInteger;/*** volatile的不适用场景*/
public class NoUseVolatile implements Runnable {volatile boolean done false;AtomicInteger realA new AtomicInteger();Overridepublic void run() {for (int i 0; i 10000; i) {flipDone();realA.incrementAndGet();}}private void flipDone() {done !done;}public static void main(String[] args) throws InterruptedException {Runnable r new NoUseVolatile();Thread thread1 new Thread(r);Thread thread2 new Thread(r);thread1.start();thread2.start();thread1.join();thread2.join();System.out.println(((NoUseVolatile) r).done);System.out.println(((NoUseVolatile) r).realA.get());}
}(3) 适用场景二
作为刷新之前变量的触发器。 5.3 volatile的两点作用
可见性读一个volatile变量之前需要先使相应的本地缓存失效这样就必须到主内存读取最新值写一个volatile属性会立即刷入到主内存。
禁止指令重排序优化解决单例双重锁乱序问题。
5.4 volatile和synchronized的关系
volatile在这方面可以看做是轻量版的synchronized如果一个共享变量自始至终只被各个线程赋值而没有其他的操作那么就可以用volatile来代替synchronized或者代替原子变量因为赋值自身是有原子性的而volatile又保证了可见性所以就足以保证线程安全。
5.5 volatile小结
volatile修饰符适用于以下场景某个属性被多个线程共享其中有一个线程修改了此属性其他线程可以立即得到修改后的值比如 boolean flag 或者作为触发器实现轻量级同步。volatile属性的读写操作都是无锁的它不能替代synchronized因为它没有提供原子性和互斥性。因为无锁不需要花费时间在获取锁和释放锁上所以说它是低成本的。volatile只能作用于属性我们用volatile修饰属性这样compilers就不会对这个属性做指令重排序。volatile提供了可见性任何一个线程对其的修改将立马对其他线程可见。volatile属性不会被线程缓存始终从主存中读取。volatile提供了happens-before保证对volatile变量 v 的写入happens-before所有其他线程后续对 v 的读操作。volatile可以使得long和double的赋值是原子的。关于long和double的原子性可以参考这篇文章。
6.能保证可见性的措施
除了volatile可以让变量保证可见性外synchronized、Lock、并发集合、Thread.join() 和 Thread.start() 等都可以保证可见性。
具体看上述happens-before原则的规定。
7.升华对synchronized可见性的正确理解
synchronized不仅保证了原子性还保证了可见性。
synchronized不仅让被保护的代码安全还近朱者赤。