书接上文,虚拟机(三)。
无锁
非阻塞同步有许多方法:如ThreadLocal,每个线程拥有各自独立的变量副本;CAS,Compare And Swap。
CAS
基于CAS的无锁并发控制,是更具性能的方式。CAS算法包括三个参数,CAS(V,E,N)。V表示要更新的变量,E表示该变量预期的值,N表示要赋予的新值。仅当V的值等于E的值时,才将V的值设为N,若不同,则该线程被告知失败,允许再次尝试或放弃操作。返回值为当前V的真实值。
java中的原子操作类的实现采用了CAS算法。java.util.concurrent.atomic包下。
LongAdder
CAS已经极大提升了性能,但欲望无止境。jdk1.8引入了LongAdder类,也是使用了CAS。但是前面提到的原子操作类的实现机制是:在一个死循环里面,不断尝试修改目标值,直到修改成功。但当影响竞争激烈,大量修改失败时,会进行多次循环尝试,性能会受到影响。
第二篇中提到了锁的优化,CAS虽然不涉及锁,但减小锁粒度等方法思路仍可以使用。仿造ConcurrentHashMap将热点数据分离,将value分离成一个数组,每个线程通过哈希等算法映射到一个数字进行计数,最终的计数结果为数组的求和累加。
java内存模型
并发程序需要保证数据访问的一致性。jvm的基本原则和特性解释并规范了多线程的各种情况。
原子性
原子代表不可分割,原子操作不可中断,不能被多线程干扰。
实现方法:volatile修饰参数。
有序性
现代CPU进行了许多优化,比如指令重排。重排会导致多线程中的语义出现不一致。
解决方法:synchronized关键字修饰方法。
可见性
可见性指的是当一个线程修改了一个变量的值时,另外一个线程可以马上得知这个修改。而因为编译器的优化,部分变量的值可能会被寄存器或Cache缓存,每个CPU都拥有独立的寄存器和Cache,从而导致其他线程无法立即发现这个修改。
解决方法:volatile关键字,或synchronized关键字。
Happens-Before原则
指令重排不可违背的原则:
- 程序顺序原则
- volatile原则
- 锁规则
- 传递性
- 线程的start()方法先于它的每个动作
- 线程的每个操作先于线程的终结
- 线程的中断先于被中断线程的代码
- 对象的构造函数执行结束先于finalize()方法