ReentrantLock锁
关于reentrantLock锁的一些问题
ReentrantLock是什么
ReentrantLock
实现了 Lock
接口,是一个可重入且独占式的锁,和 synchronized
关键字类似。不过,ReentrantLock
更灵活、更强大,增加了轮询、超时、中断、公平锁和非公平锁等高级功能。
1 | public class ReentrantLock implements Lock, java.io.Serializable {} |
ReentrantLock
里面有一个内部类
Sync
,Sync
继承
AQS(AbstractQueuedSynchronizer
),添加锁和释放锁的大部分操作实际上都是在
Sync
中实现的。Sync
有公平锁
FairSync
和非公平锁 NonfairSync
两个子类。
ReentrantLock
的特点是可重入,支持公平锁和非公平锁两种方式,从其构造方法就能看出,在传入参数为true
时,ReentrantLock
为公平锁,默认为非公平锁。
1 | public ReentrantLock() { |
阅读ReentrantLock
代码可知,它主要利用CAS
+AQS
队列来实现。以非公平锁为例,当线程竞争锁时首先使用CAS
抢占锁,成功则返回,失败则进入AQS
队列并且挂起线程;当锁被释放时,唤醒AQS
中的某个线程,从被挂起处再次尝试获取锁(当AQS
队列头节点的下一个节点不为空时,直接唤醒该节点;否则从队尾向前遍历,找到最后一个不为空的节点并唤醒),获取锁失败则再次进入队尾。
CAS
学习CAS首先了解什么是原子类
即为java.util.concurrent.atomic包下的所有相关类和API
没有CAS之前
多线程环境不使用原子类保证线程安全i++(基本数据类型) 常用synchronized锁,但是它比较重 ,牵扯到了用户态和内核态的切换,效率不高。
1 | public class T3 |
使用CAS之后
多线程情况下使用原子类保证线程安全(基本数据类型)
1 | public class T3 |
CAS是什么
CAS是什么
CAS 的全称是 Compare And Swap(比较与交换) ,用于实现乐观锁,被广泛应用于各大框架中。CAS 的思想很简单,就是用一个预期值和要更新的变量值进行比较,两值相等才会进行更新。
执行CAS操作的时候,将内存位置的值与预期原值比较: 如果相匹配,那么处理器会自动将该位置值更新为新值, 如果不匹配,处理器不做任何操作,多个线程同时执行CAS操作只有一个会成功。
原子操作 即最小不可拆分的操作,也就是说操作一旦开始,就不能被打断,直到操作完成。
CAS 涉及到三个操作数:
- V:要更新的变量值(Var)
- E:预期值(Expected)
- N:拟写入的新值(New)
当且仅当 V 的值等于 E 时,CAS 通过原子方式用新值 N 来更新 V 的值。如果不等,说明已经有其它线程更新了 V,则当前线程放弃更新。
CAS的缺点
ABA问题
CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且线程two进行了一些操作将值变成了B,然后线程two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程one操作成功。
尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的,因为在这段时间它的值可能被改为其他值,然后又改回
A,那 CAS 操作就会误认为它从来没有被修改过。这个问题被称为 CAS 操作的
"ABA"问题。ABA
问题的解决思路是在变量前面追加上版本号或者时间戳。JDK
1.5 以后的 AtomicStampedReference
类就是用来解决 ABA
问题的,其中的 compareAndSet()
方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
循环时间长开销很大
CAS 经常会用到自旋操作来进行重试,也就是不成功就一直循环执行直到成功。如果长时间不成功,会给 CPU 带来非常大的执行开销。
只能保证一个共享变量的原子操作
CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从
JDK 1.5
开始,提供了AtomicReference
类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行
CAS
操作.所以我们可以使用锁或者利用AtomicReference
类把多个共享变量合并成一个共享变量来操作。
AQS
AQS是什么?
AQS 的全称为 AbstractQueuedSynchronizer
,翻译过来的意思就是抽象队列同步器。这个类在
java.util.concurrent.locks
包下面。
AQS 就是一个抽象类,主要用来构建锁和同步器。
1 | public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { |
AQS 为构建锁和同步器提供了一些通用功能的实现,因此,使用 AQS
能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的
ReentrantLock
,Semaphore
,其他的诸如
ReentrantReadWriteLock
,SynchronousQueue
等等皆是基于
AQS 的。
AQS的底层原理
AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁 实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin,and Hagersten) 队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。在 CLH 同步队列中,一个节点表示一个线程,它保存着线程的引用(thread)、 当前节点在队列中的状态(waitStatus)、前驱节点(prev)、后继节点(next)。
CLH 队列结构如下图所示:
AQS(AbstractQueuedSynchronizer
)的核心原理图(图源Java 并发之
AQS 详解open in new window)如下:
- AQS使用一个int成员变量来表示同步状态
- 使用Node实现FIFO队列,可以用于构建锁或者其他同步装置
- AQS资源共享方式:独占Exclusive(排它锁模式)和共享Share(共享锁模式)
待续。。。