一、悲观锁、乐观锁
这里的锁,是一种多线程同步操作资源的方式,即
悲观方式
与乐观方式
。
悲观方式:
- 加锁,如:synchronized、ReentrantLock
乐观方式:
- 不加锁,如:concurrent 包的原子类
不加锁是如何保证多个线程同时操作某个资源时,执行结果正确呢?
多个线程同时操作某个资源时,为什么会有问题?
因为执行的操作不是原子操作。
A 线程的操作执行到一半,中间插入 B 的操作,那 A 的执行结果就可能有问题。
如果 A、B 的操作都是原子操作,那就不会有问题。
concurrent 包的原子类,就是将非原子操作变为了原子操作
。
这个原子操作,就是 CAS。
CAS
,即 Compare And Swap,比较和交换。
在 CPU 中通过一个 cpu 指令 cmpxchg 来完成,这是一个赋值操作
。
它的执行过程如下:
内存中的值:V
对象中的值:V1
对象需重设的值:V2
如果 V、V1 一致:将 V2 赋给 V,赋值成功
如果 V、V1 不一致:将 V 赋给 V1,赋值失败
同时执行 CAS 的线程,只有一个能更新成功,其他都会失败。
因为一旦有一个成功了,那内存中的 V 值就会变化,这就会导致其他线程中的 V1 与 V 不一致,赋值失败。
根据策略的不同,失败之后可以选择重试、报错等行为。
concurrent 包的原子类,如果赋值失败,会通过 while 循环来不断循环重试(即后面要讲的自旋锁)。
乐观锁的好处:不阻塞线程,对于简单的操作,能提高效率。
二、自旋锁、适应性自旋锁
在
乐观方式
下进行
自旋锁:自我循环的乐观锁
,即执行 CAS 失败后循环重试。
它的好处就是不阻塞线程,能提高效率。
坏处是如果一直失败,会增加开销。所以一般会有一个自旋次数的限制。
适应性自旋锁:
自旋次数限制不固定的自旋锁。
如果经常成功,那限制次数就多一点。
如果经常失败,那限制次数就少一点。
三、偏向锁、轻量级锁、重量级锁
偏向锁、轻量级锁:乐观方式
重量级锁:悲观方式
偏向锁、轻量级锁、重量级锁是 synchronized 锁的几个等级。
Jdk 6 之前
:重量级锁
Jdk 6 及之后
:偏向锁、轻量级锁、重量级锁
偏向锁通过标记位对比
,减少 CAS 操作;
轻量级锁通过CAS 操作
,减少线程阻塞;
重量级锁通过互斥量
,将等待线程都阻塞。
当只有一个线程访问锁时,synchronized 的状态是偏向锁,只通过对比 Java 对象头中的一个标记位即可进行操作。
当有第二个线程访问锁时,synchronized 的状态是轻量级锁,两个线程都要通过 CAS 来进行操作。
当有第三个线程访问锁时,synchronized 的状态是重量级锁,三个线程都要通过互斥量获得锁,来进行操作。
四、公平锁、非公平锁
在
悲观方式
下进行
公平锁:遇到锁等待时依次排队
非公平锁:遇到锁等待时先尝试插队(减少线程阻塞消耗),插队失败后再排队
五、可重入锁、不可重入锁
在
悲观方式
下进行
可重入锁:获得锁的线程内,可以再次获得锁,synchronized、ReentrantLock 都是可重入锁。
可重入锁:获得锁的线程内,不能再次获得锁。
可以一定程度上避免死锁。
六、独享锁、共享锁
在
悲观方式
下进行
独享锁:一个对象只能被一个线程加一个锁,持有者可读可写
共享锁:一个对象可以被多个线程加多个锁(只能是共享锁),持有者只能读不能写