本文是阅读《Java并发编程的艺术》后将自己记忆的理论知识和理解复写出来的产物,若有不足,欢迎指正!
java并发编程中锁的存在是来保护多线程中共享资源的安全使用。
一、volatile
volatile的意思是:不稳定的; 易变的等等,可以看出它修饰的是变量,目的是保护共享变量能够准确和一致地更新,是一种排他锁。
1.底层原理
汇编代码会多出一行Lock前缀的指令,这个操作将当前处理器缓存行的数据写回系统内存,并让其他CPU里缓存该内存地址的数据无效。
2.优化
在许多处理器中,缓存行是64字节宽,不支持部分填充缓冲行。这带来一个问题,如果一个队列数据结构的头节点和尾节点都不足64字节,有可能被读入同一个高速缓存行中,在多处理器下,其他处理器也会缓存同样的头节点和尾节点,当一个处理器试图修改头节点时会将整个缓冲行锁定,在缓冲一致性机制下,其他处理器也无法访问尾节点,严重影响出队和入队效率。
在这种情况下可以将队列的头节点和尾节点填充为64字节,防止进入同一个缓冲行,出现互相锁定的情况。LinkedTransferQueue就是这样做的。
但是在共享变量不会被频繁读写时追加字节防止互相锁定的方法成本大于收益。
二、synchronized
synchronized的意思是同步的,它确实被用来保证多个线程访问资源的同步性,并使它修饰的方法或代码块在同一时刻只有一个线程执行,而volatile只能保证变量的可见性和顺序性。synchronized是重量级锁,但Java SE 1.6对其进行了优化。
1.底层原理
在JVM中,每个对象都有一个monitor对象与之关联。同步访问一个代码块时,在编译后会将monitorenter指令插入到同步代码块的开始位置,monitorexit插入到方法结束处和异常处,线程执行到monitorenter指令时,会尝试获得其关联的monitor对象,在获得monitor对象之后,此时代码块处于锁定状态。
2.java对象头的Mark Word在不同锁状态时的对比
3.优化——锁升级
为了减少获得和释放锁带来的性能消耗,需要尽量减少不必要的加锁解锁,根据竞争情况的由弱到强,可以使用无锁、偏向锁、轻量级锁、重量级锁并能从弱到强单向升级。
(1)偏向锁
偏向锁适用于很少出现多线程竞争的情况,目的是为了减少一个线程多次获取锁的开销。偏向锁的流程如下:
(2)轻量级锁
轻量级锁的加锁和解锁以及竞争情况下锁膨胀流程如下:
(3)锁的优缺点对比
三、原子操作
原子操作是不会被线程调度机制打断的操作。 在多处理器系统中,保证跨总线宽度、跨多个缓存行和跨页表的访问是个挑战
1.使用总线锁定保证原子性
使用总线锁,即Lock#指令。当一个处理器修改共享内存时,阻塞其他处理器的请求,使其独享共享内存。
2.使用缓存锁定保证原子性
处理器有高速缓存,对其作原子操作时可以只锁定缓存对应的内存地址,在独享访问缓存的处理器修改缓存后,将缓存写回内存,并使其他处理器中的该内存地址存储的数据失效。
3.java中实现原子操作
(1)循环CAS
但这样有一些问题,比如ABA情况,开销大、只能保证一个共享变量的原子操作。
(2)锁机制
但是除了偏向锁,其他锁机制都用到了循环CAS :)