1、volatile,保证了可见性,禁止指令重排序。
(1)可见性,深入分析volatile的实现原理
处理器先将内存数据读到L1、L2缓存,再进行操作,不定期写回内存。如果写volatile变量,JVM就会向处理器发送一条Lock前缀的指令,Lock指令会使处理器将缓存写入内存,并使其他处理器的缓存无效。但其他处理器缓存的值还是旧的,为了保证各个处理器的缓存一致,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己的缓存是否过期,当处理器发现缓存被修改,就会使缓存失效,重新从内存读取。
(2)指令重排序和happens-before原则
为了充分利用多核和多级缓存,在不影响单线程执行结果的前提下,编译器和处理器可以进行指令的重排序。
happens-before原则是Java内存模型下的一些“天然的”先行发生关系,有顺序性保障。
【1】程序次序规则,在一个线程内,按照程序代码顺序,前面的操作先行发生于后面的操作(无法禁止编译重排和指令重排);
【2】管程锁定规则,一个unlock操作先行发生于后面对同一个锁的lock操作(synchronized);
【3】volatile变量原则,对一个volatile变量的写操作先行发生于后面对这个变量的读操作;
【4】线程启动规则,Thread对象的start()方法先行发生于此线程的每一个动作;
【5】线程终止规则,线程中的所有操作都先行发生于对此线程的终止检测;
【6】线程中断规则,对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
【7】对象终结规则,一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始;
【8】传递性
(3)内存屏障,volatile的使用及其原理
为了实现volatile可见性和happen-before的语义。JVM底层是通过一个叫做“内存屏障”的东西来完成。内存屏障,也叫做内存栅栏,是一组处理器指令,用于实现对内存操作的顺序限制。下面是完成上述规则所要求的内存屏障:
【1】LoadLoad 屏障
执行顺序:Load1—>Loadload—>Load2
确保Load2及后续Load指令加载数据之前能访问到Load1加载的数据。
【2】StoreStore 屏障
执行顺序:Store1—>StoreStore—>Store2
确保Store2以及后续Store指令执行前,Store1操作的数据对其它处理器可见。
【3】LoadStore 屏障
执行顺序: Load1—>LoadStore—>Store2
确保Store2和后续Store指令执行前,可以访问到Load1加载的数据。
【4】StoreLoad 屏障
执行顺序: Store1—> StoreLoad—>Load2
确保Load2和后续的Load指令读取之前,Store1的数据对其他处理器是可见的。
综上,写volatile变量时,使用lock指令强制处理器将变量写回内存,并使其它处理器的缓存失效,保证volatile的可见性;volatile满足happens-before原则,可以禁止重排序,使用内存屏障实现(实现对内存操作的顺序限制)
2、Exchanger
用于两个工作线程交换数据,一个线程到达exchange()方法调用点时,如果伙伴已经调用过了exchange(),则交换数据;否则挂起等待伙伴线程。用slot数组(为了提高并发度)实现,线程到达时,如果slot内没有线程在等待,则去index /= 2的slot里去查找,如果slot里一直没有线程在等待,最终会到达slot[0]。
3、CompletionService,融合了Executor和BlockingQueue的功能
public interface CompletionService<V> {
Future<V> submit(Callable<V> task);
Future<V> submit(Runnable task, V result);
Future<V> take() throws InterruptedException;
Future<V> poll();
Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException;
}
// 节选自jdk1.8
public class ExecutorCompletionService<V> implements CompletionService<V> {
// Executor + BlockingQueue实现
private final Executor executor;
private final AbstractExecutorService aes;
private final BlockingQueue<Future<V>> completionQueue;
// 提交任务时,先包装成QueueingFuture
private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
// 改写了FutureTask的done()方法(原本是空实现),将结果放入阻塞队列
protected void done() {
completionQueue.add(task);
}
private final Future<V> task;
}
public Future<V> take() throws InterruptedException {
return completionQueue.take();
}
}
使用场景:如果提交了一组任务,希望完成后获得结果,可以保留ExecutorService返回的List<Future>,轮询list,过于繁琐。CompletionService解决了这个问题,按照任务完成的先后顺序将Future放入队列,直接从阻塞队列取即可。