可见性的理论
就说这个线程是可见的
什么是线程的工作内存
工作内存是java内存模型提出的概念
JMM
变量是指共享变量
所有的变量都存储在主内存中
每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)
变量的源保存在哪里呢:主内存
工作内存和主内存的关系
下面的X就是三个线程的共享变量
共享变量可见性的原理
共享变量可见性的原理
要想使得线程2能及时更新X的值,则需要工作内存1的值及时刷新到主内存,然后工作内存2的值从主内存中读取出来。
两个步骤其中任何一个步骤出了差错,都会导致变量不可见。会导致数据的不准确,从而是的线程不安全。所以在编写代码的时候要保证共享变量的可见性
关于可见性
满足两点可以保证可见性
这里指语言层面,所以不包括concurrent并发包下的高级特性
可以实现互斥锁(原子性),用synchronized
但是他也有另个功能,即实现内存的可见性
Synchronized实现可见性
这样就可以保证共享变量的变化,在其他线程枷锁前,对其他线程可见(也就是其他线程能及时更新共享变量的变化)
这六个步骤可以结合刚刚的两条规定来理解。
指令重排序
重排序的概念
不理解也没关系,可以看个下面简单的例子,代码顺序,和实际执行顺序不一致,就是重排序的一种体现
看的懂上面的意思即可,有可能执行顺序和代码顺序不一样
as-if-serial
Java在单线程下一定遵循这个条件。
举个例子:
1 2行重排序,但是第三行不能重排序(不能1 2行之前执行)。多线程中可能会有问题
一个例子
package mkw.demo.syn; public class SynchronizedDemo { //共享变量 private boolean ready = false; private int result = 0; private int number = 1; //写操作 public void write(){ ready = true; //1.1 number = 2; //1.2 } //读操作 public void read(){ if(ready){ //2.1 result = number*3; //2.2 } System.out.println("result的值为:" + result); } //内部线程类 private class ReadWriteThread extends Thread { //根据构造方法中传入的flag参数,确定线程执行读操作还是写操作 private boolean flag; public ReadWriteThread(boolean flag){ this.flag = flag; } @Override public void run() { if(flag){ //构造方法中传入true,执行写操作 write(); }else{ //构造方法中传入false,执行读操作 read(); } } } public static void main(String[] args) { SynchronizedDemo synDemo = new SynchronizedDemo(); //启动线程执行写操作 synDemo.new ReadWriteThread(true).start(); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } //启动线程执行读操作 synDemo.new ReadWriteThread(false).start(); } }
第一种情况
假设在1.1后让出了cpu资源,那么这时候执行2.1,也就是读线程开始执行,这时候ready是true,执行2.2 result变成3.
输出结果是3。1.2可能在输出后再执行,但这就导致输出的时候result是3
第二种情况
先执行1.2,说明进行了指令重排序,打印了初始值 0
还可以2.1和2.2重排序,他们重排序后的结果是这样的
中间加了一个mid变量保存中间结果
只有数据依赖关系才会禁止指令重排序。
可见性分析
导致共享变量在线程间不可见的原因
synchronize实现可见性的方法
即共享变量在某个线程被改变能及时在其他线程使用之前更新
上面的方式就可以保证读写线程不会交叉执行