本文共 6590 字,大约阅读时间需要 21 分钟。
可见性:一个线程对共享变量值得修改,能够及时的被其他线程看到.
共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量.–注意区别于Java内存结构
java内存模型(Java Memory Model)描述了java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存读取变量这样的底层细节.线程1对共享变量的而修改想要被线程2 看到,必须经过如下的两个步骤:
将主内存中最新的共享变量的值更新到工作内存2中
java语言层面支持的可见性实现方式:(语言层面,所以不包括JDK1.5以后 concurrent包以下的高级特性)
synchronized能够实现:
JMM关于synchronized的两条规定:
线程解锁前对共享变量的修改在下次加锁时对其他线程可见
线程执行互斥代码的过程:
1.获得互斥锁 2.清空工作内存 3.从主内存中拷贝变量的最新副本到工作内存中 4.执行代码 5.将更改后的共享变量的值刷新到主内存 6.释放互斥锁重排序:代码书写的顺序与实际的执行顺序不同,指令重排序是编译器或者处理器为了提高程序性能而做的优化
1.编译器优化的重排序(编译器优化) 2.指令级并行重排序(处理器优化) 3.内存系统的重排序(处理器优化)比如 代码顺序:
int number = 1;int result =0;
而执行顺序:
int result =0;int number = 1;
as-if-serial:无论如何进行重排序,程序的执行的结果应该与代码顺序的结果一致(java编译器,运行时和处理器都会保证java在单线程下遵循as-if-serial语义)
(不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。java内存中的变量都有指针引用,上下文引用成链,这个链是不会被打乱重排序的,**没有数据依赖关系的代码,才会被重排序,所以在单线程内部重排序不会改变程序运行结果)int num1 = 1 ; int num2 = 2;int sum = num1+num2;
单线程:第1,2行的顺序可以重排,但是第三行不能,因为存在数据依赖关系,重排序不会给单线程带来内存可见性的问题.多线程中程序交错执行,重排序可能会造成内存可见性的问题.
/** * @dare 2018年7月20日 * @description * /public class SynchronizedAndVolatileTester { // 共享变量 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) { SynchronizedAndVolatileTester synDemo = new SynchronizedAndVolatileTester(); // 启动线程执行写操作 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->2.1->2.2->1.2 result:3 1.2->2.1->2.2->1.1 result:0 1.1->1.2->2.1->2.2 result:6 2.2–>2.1->1.1->1.2 result:0 …. 2.1和2.2重排序后:int mid = number*3;if(ready){ result = mid;}
分析:
导致共享变量在线程间不可见的原因: 1.线程的交叉执行 2.重排序结合线程交叉执行 3.共享变量更新后的值没有在工作内存与主内存间及时更新安全的代码::
/ 写操作 public synchronized void write() { // ready = true; // 1.1 number = 2; // 1.2 } // 读操作 public synchronized void read() { System.out.println("result的值为:" + result); }
它是如何保证安全执行的?
不可见原因 | synchronized解决方案 |
---|---|
线程的交叉执行 | 原子性 |
重排序结合线程交叉执行 | 原子性 |
共享变量更新后的值没有在工作内存与主内存间及时更新 | 可见性 |
注意:不加synchronized ,共享变量也可能在线程间是可见性
- 不能保证volatile变量复合操作的原子性深入来说:通过加入内存屏障和禁止重排序优化来实现的.
线程写volatile变量过程:
1.改变线程内存中volatile变量副本的值 2.将改变后的副本的值从工作内存中刷新到主内存 线程读volatile变量的过程: 1.从主内存中读取volatile变量的最新值到线程的工作内存中 2.从工作内存中读取volatile变量的副本public class VolatileDemo { private int number = 0; public int getNumber(){ return this.number; } public void increase(){ try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.number++; } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub final VolatileDemo volDemo = new VolatileDemo(); for(int i = 0 ; i < 500 ; i++){ new Thread(new Runnable() { @Override public void run() { volDemo.increase(); } }).start(); } //如果还有子线程在运行,主线程就让出CPU资源, //直到所有的子线程都运行完了,主线程再继续往下执行 while(Thread.activeCount() > 1){ Thread.yield(); } System.out.println("number : " + volDemo.getNumber()); }}
假设有一瞬间number =5;之后
1.线程A读取number的值 2.线程B读取number的值 3.线程B执行加1操作 4.线程B写入最新number的值 此时: 主内存中:number =6; 线程B工作内存:number =6; 线程A工作内存:number = 5;(不会被更新) 5.线程A执行加1操作 6.线程A写入最新的number的值(到主内存) –>两次number++只增加了1;保证volatile自增操作的原子性:
package mkw.demo.vol;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class VolatileDemo { private Lock lock = new ReentrantLock();//可重入锁 private int number = 0; public int getNumber(){ return this.number; } public void increase(){ try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } lock.lock(); try { this.number++; } finally { lock.unlock(); } } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub final VolatileDemo volDemo = new VolatileDemo(); for(int i = 0 ; i < 500 ; i++){ new Thread(new Runnable() { @Override public void run() { volDemo.increase(); } }).start(); } //如果还有子线程在运行,主线程就让出CPU资源, //直到所有的子线程都运行完了,主线程再继续往下执行 while(Thread.activeCount() > 1){ Thread.yield(); } System.out.println("number : " + volDemo.getNumber()); }}
要在多线程中安全的使用volatile变量,必须同时满足:
1.对变量的写入操作不依赖当前值 - 不满足:number++ ,count = count*5; - 满足:boolean变量,记录温度变化的变量等 2.该变量没有包含在具有其他变量的不变式中 - 不满足 :不变式 low < up整理自: