Java 多线程的 volatile 和 AQS
Java 内存模型(Java Memory Model,JMM
)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了 Java 程序在各种平台下对内存的访问都能保证效果一致的机制及规范。
JMM
是一种规范,是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。目的是保证并发编程场景中的原子性、可见性和有序性。
-
原子性:
在 Java 中,为了保证原子性,提供了两个高级的字节指令Monitorenter
和 Monitorexit。这两个字节码,在 Java 中对应的关键字就是Synchronized
。因此,在 Java 中可以使用Synchronized
来保证方法和代码块内的操作是原子性的。 -
可见性:
Java 中的Volatile
关键字修饰的变量在被修改后可以立即同步到主内存。被其修饰的变量在每次使用之前都从主内存刷新。因此,可以使用Volatile
来保证多线程操作时变量的可见性。只不过实现方式不同。 -
有序性:
在 Java 中,可以使用Synchronized
和Volatile
来保证多线程之间操作的有序性。区别:Volatile
禁止指令重排。Synchronized
保证同一时刻只允许一条线程操作。
一、volatile 底层实现
1.1 作用
保证数据的“可见性”:被 volatile
修饰的变量能够保证每个线程获取该变量的最新值,从而避免出现数据脏读的现象。
禁止指令重排:在多线程操作情况下,指令重排会导致计算结果不一致。
1.2 底层实现
观察加入 volatile
关键字和没有加入 volatile
关键字时所生成的汇编代码发现,加入 volatile
关键字时,会多出一个 lock
前缀指令
lock
前缀指令实际上相当于一个内存屏障(也称内存栅栏),内存屏障会提供 3 个功能:
- 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;
- 它会强制将对缓存的修改操作立即写入主存;
- 如果是写操作,它会导致其他 CPU 中对应的缓存行无效。
1.3 单例模式中的 volatile 的作用
防止代码读取到 instance
不为 null
时,instance
引用的对象有可能还没完成初始化。
class Singleton {
private volatile static Singleton instance = null; // 禁止指令重排
private Singleton() {
}
public static singleton getInstance() {
if(instance == null) {
// 减少加锁的损耗
synchronized(Singleton.class){
if(instance == null) {
// 确认是否初始化完成
instance = new Instance();
}
}
}
return instance;
}
}
二、AQS 思想
AQS
的全称为(AbstractQueuedSynchronizer
)抽象的队列式的同步器,是一个用来构建锁和同步器的框架,使用 AQS
能简单且高效地构造出应用广泛的大量的同步器,如:基于 AQS 实现的 lock
,CountDownLatch
、CyclicBarrier
、Semaphore
需解决的问题:
状态的原子性管理线程的阻塞与解除阻塞队列的管理
AQS
核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS
是用 CLH
(虚拟的双向队列)队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
2.1 lock
是一种可重入锁,除了能完成 synchronized
所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询请求、定时锁等避免多线程死锁的方法。默认为非公平锁,但可以初始化为公平锁;通过方法 lock()
与 unlock()
来进行加锁与解锁操作;
2.2 CountDownLatch
通过计数法(倒计时器),让一些线程堵塞直到另外一个线程完成一系列操作后才被唤醒;该工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。具体可以使用 countDownLatch.await()
来等待结果。多用于多线程信息汇总。
2.3 CompletableFuture
通过设置参数,可以完成过 CountDownLatch
同样的多平台响应问题,但是可以针对其中部分返回结果做更加灵活的展示。
2.4 CyclicBarrier
字面意思是可循环(Cyclic
)使用的屏障(Barrier
)。它要做的事情是,让一组线程达到一个屏障(也可以叫做同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过 CyclicBarrier
的 await()
方法。可以用于批量发送消息队列信息、异步限流。
2.5 Semaphore
信号量主要用于两个目的,一个是用于多个共享资源的互斥作用,另一个用于并发线程数的控制。SpringHystrix
限流的思想
三、happens-before
用来描述和可见性相关问题:如果第一个操作 happens-before
第二个操作,那么我们就说第一个操作对于第二个操作是可见的
常见的 happens-before
:volatile
、锁、线程生命周期。