前言
提到多线程就不可表面的会提到数据争用,提到数据争用就会自然而然的聊到多线程,本篇就对java多线程的内存可见性做一个探讨
共享变量在内存中的可见性
可见性:一个线程对共享变量值得修改,能够及时的被其他线程看到
共享变量:如果一个变量在多个线程的线程内存中都存有副本,那么这个变量就是这几个线程的共享变量
如果想要了解共享变量,那么就要先知道java的内存模型
java内存模型
java内存模型描述了java程序中各种变量(线程共享变量)的访问规则,以及在jvm中将变量 存储到内存和从内存中度去除变量这样的底层细节
注意:所有的变量都存储在主内存中,每个线程都有自己独立的工作内存,里面保存了该线程使用到的变量的副本(主内存中该变量的一份拷贝)
java线程的两条规定:
- 每个线程对共享变量的操作都必须把共享变量读取到自己的内存中进行操作,而不能直接在主内存中进行操作
- 不同线程之间无法访问其他线程中的线程内存的变量,线程之间的变量值得传递通过主内存来进行
下面这个是共享变量可见性实现的原理的gif
小结
实现共享变量的两个点
线程修改后的共享变量值必须能够及时从线程工作内存刷新到主内存
其他线程需要及时的吧主内存的最新值读取到自己的线程内存中
synchronized关键字实现可见性
synchronized的同步性相信很多人都已经知道了,这里着重对于synchronized的可见性进行讲解
JVM对synchronized的规定:
- 线程解锁前,必须把工作内存中的共享变量刷新到主内存中
- 线程加锁时,将清空线程内存中的共享变量,再从主内存中拿共享变量(注意:加锁和解锁使用的是同一个对象)
接下来是一段代码,用于展示未添加synchronized关键字会出现的问题
/**
* synchronized能够实现原子性(同步)、可见性
*
* @author xuwenjin
*/
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 WriteReadThread extends Thread {
private boolean flag = false;
public WriteReadThread(boolean flag){
this.flag = flag;
}
@Override
public void run() {
if (flag) {
write();
}else {
read();
}
}
}
public static void main(String[] args) {
SynchronizedDemo demo = new SynchronizedDemo();
//启动线程执行写操作
demo.new WriteReadThread(true).start();
//启动线程执行读操作
demo.new WriteReadThread(false).start();
}
}
这块代码分为两块,分别是read()和write(),在read中进行ready和number值得改变,二在write中通过判断ready进行number值得修改,如果直接执行的话,会出现很多的结果,接下来我对几个结果进行列举
- result的值为6 (正常情况) 1.1 --> 1.2 --> 2.1--> 2.2
- result的值为3 (当线程1吧wirte()的1.1执行完成之后,线程2开始执行read()方法)
- result的值为0 (1.1跟1.2重排序) 1.2 --> 2.1 --> 2.2 --> 1.1
- ...还有一些,就不一一列举了
导致这些情况出现的原因无非是因为:1.指令重排, 2.在线程内存中修改的值并未及时同步到主内存中
接下来使用synchronized来对上面的代码进行修改
/**
* 写操作
*/
public synchronized void write() {
ready = true; //1.1
number = 2; //1.2
}
/**
* 读操作
*/
public synchronized void read() {
if (ready) { //2.1
result = number * 3; //2.2
}
System.out.println("result:" + result);
}
注意:synchronized加在方法上锁住的是this对象,也就是当前对象
经过上面的修改后,结果就只会出现两个了
- result的值为6 正常情况(read()先执行完,然后write()方法在执行)
- result的值为0, write()先执行了,然后是read()方法再执行
如果要确定read和write的执行顺序,那么使用Thread.sleep()来对两个线程的启动顺序进行一下控制即可