一. 场景原子性与指令重排
双重检查单例最外层的null判断无需加锁任意线程可判断,new指令的操作顺序重排列之后会引起instance先分配了指针,但指向的区域未初始化,时间片分配其他线程即使当前锁未释放其他线程也可以最外层判断非null直接返回空指针,用volitile禁止重排则可。
二. montor对象
- 条件变量类似,创建condition之后执行wait()方法,线程会进入相应条件的等待队列。
- volitile变量只能保证可见性,不能保证原子性,存在竞态条件时要配合锁使用
三. 并发编程多任务
- 对于简单的并行任务,你可以通过“线程池 +Future”的方案来解决;如果任务之间有聚合关系,无论是 AND 聚合还是 OR 聚合,都可以通过 CompletableFuture 来解决(任务二依赖任务三的结果执行,而任务一又是等任务二和三执行的结果再执行);而批量的并行任务,则可以通过 CompletionService 来解决(内部维护阻塞队列,可以认为多future的结果放到阻塞队列里,谁先结束谁执行)。invokeAll方法可以批量提交任务,并返回批量结果,结果要么中断要么成功。
- 在fork-join任务中的Join不会阻塞,它保存当前任务,因此只有在join()创建的子任务完成后才可以继续计算。
WorkerThread指出,直到子任务完成,任务才被阻塞,因此该任务开始在子任务上工作。
四. 并发技巧清单
- 所有的并发问题都可以归结为如何协调对并发状态的访问,可变状态越少越容易确保线程安全性。
- 将数据封装在对象中,更易于维持不变性条件,用锁来保护每一个可变变量。
- 当保护一个不变型条件中的可变变量时要使用同一把锁。
- 执行复合操作时要持有锁。
- 出现中断时,线程可以推迟处理中断请求在完成任务后抛出interruptedException,除非知道中断对线程的含义否则捕获到线程后要interrupt恢复中断状态。
五. 并发机制底层实现原理
-
volitile
1. 使用volitile修饰的共享变量进行写操作会多出一行汇编代码lock,lock的作用:
(1). 将当前处理器缓存行的数据写回到系统内存(锁定内存区域的缓存,并通过缓存一致性确保修改原子性,invalidate状态下响应读请求会把读缺失广播到总线,最后由主存发出响应,invalidate下的缓存copy到cache里)缓存一致性MESI,LOCK指令,不同的硬件本身的内存屏障指令不同,jvm编译字节码指令统一四个loadload,storestore,storeload,loadstore(volitile字段通过putstatic指令赋值根据volitile标记进行store,store完执行storeLoad方法,内部执行fence不同的系统嵌入不同的汇编指令源码,x86下一般使用lock指令一般只有storeLoad屏障而且没有invalidate queue)不能保证原子性也可理解为cpu修改值当前状态为E,同时收到其他cpu的请求read invalidate,则状态变更为I失效。
jvm由c++实现,底层会转化为对c++ volatile变量的读写,绕过