目录:
- 不可见性是什么?
- volatile 可以保证原子性吗?
- . 重排的示例和作用?
- Happends - before 是什么?
- volatile与synchronized 区别?
- 参考
1.不可见性是什么?
1.1不可见性案例
/**
* 多线程修改变量 会出现 修改值之后不可见性
*/
public class VisibilityDemo1 {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
while (true) {
if (thread.isFlag()) {
System.out.println(" 进入 -- ");
}
}
}
}
class MyThread extends Thread {
private boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(1000); // 标记1
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println(" flag = " + flag);
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
控制台会不会打印输出:" 进入 –" ?,我估计应该有一部分读者会认为答应。
实际的输出如下:
1.2 如何解决这个问题
解决方案1: 去掉 标记1 代码
解决方案2: private volatile boolean flag = false;
解决方案3(下一篇 synchronized 进行分析 ): 加入 synchronized
while (true) {
synchronized (VisibilityDemo1.class) {
if (thread.isFlag()) {
System.out.println(" 进入 -- ");
}
}
}
1.3 原理分析
分析之前需要知道JMM(JAVA 内存模型) :
1.JAVA 内存模型 描述了java 程序中 各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。
2. JMM 有以下规定:
1. 所有的共享变量都存储于主内存。这是所说的变量指的是实例变量和类变量。不包含局部变量,因为局部变量时线程私有的。因此不存在竞争问题。
2.每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。
3. 线程对变量的所有的操作(读,取) 都必须在工作内存中完成,而不能直接读写主内存中的变量。
4. 不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值得传递需要通过主内存中转完成
3. 主内存 和 工作内存示意图
-
开始问题分析
问题发生的路径:
1. 子线程 thread 从主内存读取到数据放入其对应的工作内存
2.将 flag 的值 更改为true , 但是这个时候 flag的值还没有写回主内存
3.此时main 方法读取到了flag的值为false
4. 当子线程t将 flag的值写回去后,但是main 函数里面的while(true) 调用的是系统比较底层
的代码,速度快,快到没有时间再去读取主内存中的值
所以while(true) 读取到点值一直是false
分析 解决方案2:(重要 重要 重要)
1.1 子线程t 从主内存读取到数据放入其对应的工作
1.2 将flag的值更改为true ,但是这个时候flag的值还没有写主内存
1.3 此时main方法 读取到了flag的值为false
1.4 当子线程t将flag的值写回去后,失效( 嗅探技术 )其他线程对此变量副本
1.5 再次对flag进行 操作的时候线程会从主内存读取最新的值,放入到工作线程中
总结: volatile 保证不通线程对共享变量操作的可见性,也就是说一个线程修改了volatile的修饰的变量,当修改写会内存时,另外一个线程立即看到最新值.
2. volatile 可以保证原子性吗??
2.1 volatile 可以保证原子性吗?
答:不可以, volatile是可见但是线程不安全。 // 解决办法 synchronized 修饰
public static void main(String[] args) {
Runnable target = new MyTarget();
for (int i = 1; i <= 100; i++) {
new Thread(target, " 第" + i + "线程").start();
}
}
}
class MyTarget implements Runnable {
private volatile int count = 0;
@Override
public void run() {
for (int i = 1; i <= 10000; i++) {
count++;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
// 输入结果 基本不可能等于 100*10000
分析一下:
1、从主内存中读取数据到工作内存
2、对工作内存中的数据进行++ 操作
3、将工作内存中的数据写回到主内存
也可以从编译的字节码看下
描述:
1. 假设此时X的值100,线程A需要对改变量进行自增1的操作,
首先它需要从主内存中读取变量x的值。由于cpu的切换关系,此时cpu的执行权被切换了B线程。 A线程就需出于就绪状态。B 线程出于运行状态
2 线程b也需要从主内存中读取X变量的值,由于线程A没有对X值做任何
因此B读取到点数据还是100
3 线程b工作内存中x 执行了+1操作,但是未刷新之主内存中
4 此时CPU的执行权切换到了A线程上,由于此时线程B没有将工作内存中的数据刷新到主 内存,因 此A线程工作内存中的变量值还是1-- 没有失效。
5 线程B将101 写入到主内存
6 线程A将101 写入到主内存
虽然计算量2次,但是只对A进行了1次修改。
3.重排的实例和作用?
-
什么是重排: 为了提高性能,编译器和处理器常常会对既定代码执行顺序进行指令排序.
有三种情况:
1.1 编译器优化的重排序,编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
1.2 指令级并行嗯重排序,现代处理器采用了指令级并行技术来将多条指令重叠执行,如果不存在数据依赖性,处理器可以改变 语句对应机器指令的执行顺序。
1.3 内存系统的重排序,由于处理器使用的缓存和读/写 缓冲区 ,这使得加载和存储操作看上去可能是在乱序执行的。
重点:重排序不是万能的。 也会出问题。
其实这个地方还有 重要点 重要点 重要点.
重排序 返回内存地址引用 和 初始化构造方法详细情况.
Happends - before 是什么?
通过 happens-before的概念,描述操作之间内存的可见性。
如果一个操作执行的结果需要对另外一个操作可见,那么这两个操作之间必须存在happeds-before关系。
两个操作可以是在一个线程内,也可以在不同线程之间。
一共有六项规则:
1.程序顺序规则(单线程规则)
解释:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
- 同一个线程中前面的所有写操作对后面的操作可见。
2. 锁规则(Synchronized, Lock 等)
解释: 对一个锁的解锁,happens-before于随后对这个锁的加锁。
如果线程1解锁monitor,接着线程2锁定了a,那么线程1解锁a之前的写操作都对线程2可见(线程1和线程2可以是同一线程)
3. volatile 变量规则
解释 对一个锁的解锁,happends-before于随后对这个锁的加锁。
如果线程1写入了 volatile变量v(临界资源) , 接着线程2锁定了a,那么,线程1解锁a之前的写操作都对线程2可见(线程1和线程2可以是同一线程)
4: 传递性:
解释: 如果A happens-before B 且 B happens-before C ,那么 A happens-before C
A -> B , B ->C 且 A-C
5 start()规则
解释: 如果线程A执行操作ThreadB.start() 启动线程B,那么线程A的TheadB.start() 可以看到 A线程的变量。
假定 线程线程A在执行过程中,通过执行ThreadB.start() 来启动线程B,那么线程A对共享变量的修改在接下来线程b开始执行前对线程B可见。 注意: 线程B 启动之后,线程A在对变量修改线程B未必可见。
6 join 规则
解释: 如果线程A 执行操作ThreadB.join 并成功返回。那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成返回
线程t1 写入的所有变量,在任意其他线程t2调用t1.join,或者t1.isAlive成功返回后,都对t2可见。
demo: https://download.csdn.net/download/IT_peng/12203228
参考:
https://www.bilibili.com/video/av81907443?p=18 (这个视频 质量挺高)
java并发编程的艺术
https://www.cnblogs.com/54chensongxia/p/11806836.html
https://blog.csdn.net/anjxue/article/details/51038466?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task