一、回顾–JVM程序运行原理
jvm内存存储结构:
线程共享区存在多个线程的竞争和协同的问题,java多线程核心技术就是线程安全机制和协作机制,虚拟机的内存模型是针对多线程问题提供的java语言规范。
一个例子,揭示线程可见性问题:
package shen.example.demo.mutithread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* <pre>
* 作者:shenliang
* 项目:shen.example.demo.mutithread
* 说明:线程可见性
* 日期:2020年04月28日
* 备注:
* </pre>
*/
public class Demo2 {
private boolean flag = true;
private Lock lock = new ReentrantLock(true);
public static void main(String[] args) {
Demo2 demo = new Demo2();
new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
while(demo.flag){//指令重排序 优化为 if(flag){while(true){}}
//demo.lock.lock();
//synchronized (this){}
i++;
//demo.lock.unlock();
}
System.out.println("执行完了"+i);
}
}).start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo.flag = false;
System.out.println("flag 置为 false了");
}
}
二、同步规则定义-Happen-before原则
java语言规范中规定了同步规则, 确定存在冲突(竞态)的两个操作的执行顺序的问题。由具体的虚拟机实现。
共享变量在jvm的内存结构,和指令重排序会导致多线程(线程间操作)下执行顺序的问题,内存模型的规定正是为了解决这些问题出现的。
官方文档参考:
https://docs.oracle.com/javase/specs/jls/se14/html/jls-17.html
文档中happen-before原则使用翻译:
1.监视器上的解锁发生在该监视器上的每个后续锁定之前。
2.在对该字段的每次读取之前,都会先写入该 volatile字段。
3.在启动线程中的任何操作之前,都会先发生start()对线程的调用。
4.线程中的所有最后操作都发生在任何其他线程成功从join()该线程的返回之前。
5.任何对象的默认初始化都发生在程序的其他任何动作(默认写操作除外)之前
三、volatile关键字–线程可见性
从上面的happen-before原则可以看出,jvm具体实现需要保证其修饰的共享变量是"可见"的。
那么虚拟机怎么保证的呢?
以oracle的虚拟机为例:
1.禁止缓存
volatile变量在字节码层面访问控制符会加一个ACC_VOLATILE 表示该变量不能缓存。
2.对volatile变量的指令不做重排序