一、volatile的理解
volatile是Java虚拟机提供的轻量级的同步机制
volatile的三大特性:保证可见性,不保证原子性,禁止指令重排
二、JMM(Java内存模型)
JMM(Java内存模型Java Memory Model)本身是一种抽象的概念并不真实存在,在java中,所有实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享(本文使用“共享变量”这个术语代指实例域,静态域和数组元素)。局部变量(Local variables),方法定义参数(java语言规范称之为formal method parameters)和异常处理器参数(exception handler parameters)不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。
- 可见性
Java线程之间的通信由Java内存模型(Java Memory Model本文简称为JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。Java内存模型的抽象示意图如下:
从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:
首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
然后,线程B到主内存中去读取线程A之前已更新过的共享变量。
下面通过示意图来说明这两个步骤:
//可见性测试
public class VolatileTest {
public static void main(String[] args) {
vTest v=new vTest();
Thread thread = new Thread(() -> {
try {
TimeUnit.MILLISECONDS.sleep(60);
} catch (InterruptedException e) {
e.printStackTrace();
}
v.change();
System.out.println(Thread.currentThread().getName() + ":" + v.a);
});
thread.start();
while (v.a ==0){
}
System.out.println(Thread.currentThread().getName()+":"+v.a);
}
}
class vTest{
//volatile int a = 0;
int a = 0;
public void change(){
this.a = this.a +1;
}
}
- 原子性:number++在多线程中就算加了volatile也不是安全的.
解决方法:第一种加sync关键字,第二种使用juc.AtomicInteger。
//原子性测试
AtomicInteger atomicInteger = new AtomicInteger();
public static void main(String[] args) {
syntest s= new syntest();
Thread[] threads = new Thread[10];
for(int i=0;i<10;i++){
threads[i] = new Thread(()->{
// synchronized (s){
// for(int j=0;j<10;j++){
// try {
// Thread.sleep(10);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// s.setA(s.getA()+1);
// System.out.println(Thread.currentThread().getName()+":"+s.getA());
// }
// }
for(int j=0;j<100;j++){
s.atomicInteger.getAndIncrement();
System.out.println(Thread.currentThread().getName()+":"+s.atomicInteger);
}
});
}
Arrays.asList(threads).forEach(t->t.start());
}
3.有序性
指令重排:
在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。重排序分三种类型:
①编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可 以重新安排语句的执行顺序。
②指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存 在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
③内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载 和存储操作看上去可能是在乱序执行。
从java源代码到最终实际执行的指令序列,会分别经历下面三种重排序:
利用volatile指令禁重排的特性,保证在多线程环境下程序运行的有序性。
public class Singleton5 {
private Singleton5(){}
private static volatile Singleton5 instance;
public static Singleton5 getInstance(){
if(instance == null){
//加上同步锁
synchronized (Singleton5.class){
if(instance == null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Singleton5();
}
return instance;
}
}
return instance;
}
}