1.JUC介绍
在Java5.0以上的版本新增了java.util.concurrrent包,此包在并发编程中有很多实用的工具,包括线程池、异步、IO和轻量级框架等实现;
2.并发的三个特性回顾
原子性
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
可见性
指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性
即程序执行的顺序按照代码的先后顺序执行。
3.volatile 关键字
volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。
保证可见性,不保证原子性
当写一个volatile变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中去,但是这个写会操作会导致其他线程中的缓存无效;
禁止指令重排
在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行;
volatile不适用的场景
volatile不适合复合操作
i++不是一个原子性操作,可以由读取、加、赋值3步组成,需要配合synchronized或者lock来实现;.
volatile原理
volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用memory barriers(内存屏障)来实现的。观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
内存屏障的作用
- 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
- 它会强制将对缓存的修改操作立即写入主存;
- 如果是写操作,它会导致其他CPU中对应的缓存行无效。
单例模式的双重锁为什么要加volatile
public class App {
private volatile static App instance;
public static App getInstance() {
if(instance==null) {
synchronized (App.class) {
if(instance==null) {
instance= new App();
}
}
}
return instance;
}
}
需要volatile关键字的原因是,在并发情况下,如果没有volatile关键字,在第7行会出现问题。instance= new App();可以分解为3行伪代码
a.memory = allocate() //分配内存
b. ctorInstanc(memory) //初始化对象
c. instance = memory //设置instance指向刚分配的地址
上面的代码在编译运行时,可能会出现重排序从a-b-c排序为a-c-b。在多线程的情况下会出现以下问题。当线程A在执行第7行代码时,B线程进来执行到第2行代码。假设此时A执行的过程中发生了指令重排序,即先执行了a和c,没有执行b。那么由于A线程执行了c导致instance指向了一段地址,所以B线程判断instance不为null,会直接跳到第6行并返回一个未初始化的对象。