实现了Lock接口,是一种可重入的独占锁,它和synchronized都是一种互斥锁,但是比synchronized有更多的特性,比如手动的加锁和解锁、支持加锁的公平性;ReentrantLock是基于**AQS(AbstractQueuedSynchronizer)**框架实现的;
ReentrantLock lock = new ReentrantLock(false);//true为公平锁,无参构造函数为非公平锁;
lock.lock();//加锁
lock.unlock();//解锁
ReentrantLock类介绍:
在ReentrantLock内部定义了Sync抽象类,其继承AbstractQueuedSynchronizer(AQS)抽象类,对该抽象类的部分方法做了实现;在ReentrantLock内部又定义了两个子类,都是实现了Sync抽象类,也间接实现了AQS:(用到的设计模式为:模板模式,子类根据需要来重写或实现父类方法)
- NonfairSync非公平锁的实现;
- FairSync公平锁的实现;
在看一下ReentrantLock的构造函数:
//有参
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//无参
public ReentrantLock() {
sync = new NonfairSync();
}
定义了公平锁和非公平锁实现的类。
再看ReetrantLock源码前提是先了解一下AQS框架:
AQS具有的特性:
- 阻塞等待队列;
- 共享/独占;
- 公平与非公平;
- 可重入;
允许中断;(lock.lockInterruptibly();)这个需要我们手动中断线程,
if (failed)
cancelAcquire(node);
这个代码在这块是起到作用的,就是把当前中断节点赋值waitState=CANCEL;源码已看过,并且理解;
AQS核心有四个非常重要的元素:
- 自旋;
- LockSuport(用于线程的阻塞与唤醒);
- CAS;
- Queue队列
除了Lock之外,java.concrrent.util包下的Latch、Barrier、BlockingQueue等都是基于AQS实现的:一般是通过定义内部类Sycn继承AQS,然后再调用Sycn类中的方法;
AQS中维护了一个属性private volatile int state;来维护资源的可用性(在Lock中只有为0才表示资源可用,也就是锁处于可用状态);
AQS定义了两种资源访问方式:
- Exclusive-独占,只有一个线程可以访问,如ReentrantLock;
- Share-共享模式,多线程可以同时访问,如Semapore/CountDownLatch;
AQS定义了两种队列:
同步等待队列:
同步等待队列也称CLH队列,CLH队列是以发明人Craig、Landin、Hagersten首字母命名的队列,是基于双向列表结构的队列;AQS中定义了节点对象Node,在CLH同步队列中,一个节点表示一个线程,它保存着线程的引用(thread)、状态(waitStatus)、前驱节点(prev)、后继节点(next);
条件等待队列(Condition Queue):
Condition是多线程间协调通信的工具,一些线程在等待某个条件(Condition),只有线程具备该条件时才会被唤醒,从而重新争夺锁;
接下来我们看一下ReetrantLock源码来了解其实现原理:
先从lock.lock();加锁方法开始看起
public void lock() {
sync.lock();
}
sync这个对象要看创建的是公平锁还是非公平锁;如果是非公平锁则会进入NonfairSync类的方法:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
而公平锁FairSync
里的加锁方法为:
final void lock() {
acquire(1);
}
两者的区别在于非公平锁一进入就开始获取锁,如果获取失败,则再获取一次,再失败就加入同步等待队列中;而公平锁是先看看有没有在排队的线程,如果没有则尝试获取锁,如果有排队的,则进入等待队列最后。
尝试获取锁的的方法:用的是CAS,是JDK自带的工具方法,底层使用了操作系统,保证了CAS更新值得原子性。可以从上面可以看出更新的属性值是AQS中的state,如果是0则代表可以获取锁,则会把0更新为1;如果更新失败则返回false;
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
如果上面CAS替换成功,则会执行方法:
就是把当前线程替赋值给AQS中的private transient Thread exclusiveOwnerThread属性,执行完成之后代表当前线程获取锁成功;
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
如果上面的非公平获取锁失败,则会执行方法:
//尝试再获取锁, 如果获取失败在加入到等待线程之中;
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
先来看看方法。此时参数为1:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
方法:
逻辑:
先获取AQS的state属性,看看是否等于0,如果等于0则表示当前锁可以获取,执行获取锁的逻辑;如果当前线程等于已持有锁的线程,就会state+1;
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//CAS替换state
if (compareAndSetState(0, acquires)) {
//把AQS中的获取锁的线程属性赋值为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
//如果当前线程就是获取锁的线程,则直接对state+1,不需要加其它的//安全操作,因为只有当前线程能操作state值
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//如果上面失败,则返回获取锁失败
return false;
}
如果获取锁失败,则又会执行下面方法来构建等待队列节点:
//上面已经看了Node节点的重要属性线程的引用(thread)、状态(waitStatus)、前驱节点(prev)、后继节点(next)、Node nextWaiter(标记是SHARED节点还是EXCLUSIVE节点)
;
private Node addWaiter(Node mode) {
//创建节点对象,给thread和nextWaiter属性赋值,分别为当前线程和//EXCLUSIVE独占节点
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//尾部节点
Node pred = tail;
//如果尾部节点不为Null,则需要把当前节点设置到等待队列尾部
if (pred != null) {
//设置当前节点的前驱节点为之前的tail
node.prev = pred;
//CAS把AQS中的tail属性设置为当前节点
if (compareAndSetTail(pred, node)) {
//如果上面CAS成功则,会把之前的tail节点的后驱节点设置为当前节点
pred.next = node;
return node;
}
}
//如果同步等待队列还没初始化,则先初始化一个队列,队头为属性值为null的Node节点;再把当前节点加到队尾,并设置AQS的tail属性为当前节点。用到了自旋;
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
如果Node节点已经构建完成并加入了同步等待队列,则会执行方法:
//这一步主要的作用就是阻塞当前线程;
//从这个代码中可以看出只有当节点的前驱节点为head节点后才能调用tryAcquire(arg)来获取锁,如果获取成功则之前的头节点剥离,当前节点成功为头节点,所以非公平锁的也是从队列上从前往后获取节点获取锁的,唤醒线程也需要从队列的前面到后面进行唤醒
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//自旋来获取锁,和设置当前节点的前驱节点为SIGNAL(可唤醒)状态
for (;;) {
//获取当前节点的前驱节点
final Node p = node.predecessor();
//如果当前节点的前驱节点是head头部节点,则尝试获取锁
if (p == head && tryAcquire(arg)) {
//如果获取锁成功,则设置当前节点为head,并且设置节点属性为Null,这样一定保证Head节点的属性为null;
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//把当前节点的前驱节点的状态设置成可唤醒状态,这样的好处就是保证前面的节点先执行,并且这个方法就会把跳过状态为CANCELLED的节点,这也是最大的好处;
if (shouldParkAfterFailedAcquire(p, node) &&
//调用LockSupport.park(this);是线程处在等待状态;
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//把当前节点的前驱节点的状态设置成可唤醒状态的方法
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
以上为获取锁的全部逻辑,但是说的是非公平锁,公平锁是一上来先判断当前是否有等待队列,如果有则加入等待队列并且头节点的后置节点不是当前节点,则会加入到等待队列中等待唤醒,否则直接尝试获取锁。
接下来看一下lock.unlock()方法;
public void unlock() {
sync.release(1);
}
再执行方法:这个方法主要是把AQS的state-1,private transient Thread exclusiveOwnerThread属性设置为null;并且唤醒头节点的下一个节点,如果头节点的下个节点不可用,从等待队列的尾部开始遍历获取以当头节点作为前驱节点的节点进行唤醒。
public final boolean release(int arg) {
//state-1,private transient Thread exclusiveOwnerThread属性设置为null
if (tryRelease(arg)) {
Node h = head;
//如果头节点不为null,并且waitStatus != 0,则进行线程的唤醒,从加锁的逻辑可以看出,这个waitStatus肯定为SIGNAL状态;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
方法:从这个方法可以看出,几次加锁,要对应几次的释放锁;
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
方法:设置头节点的waitStatus值为0;唤醒等待线程
private void unparkSuccessor(Node node) {
// 设置头节点的waitStatus值为0没看出这个的作用,估计想保持头节点的纯洁性
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//找到头节点的后置节点,然后判断此节点是否为可唤醒状态,如果是,则直接唤醒,否则从等待队列的尾部开始遍历获取以当头节点作为前驱节点的节点进行唤醒。
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
ConditionObject类分析:
AQS内部类,实现了Condition接口的await()、singal()、singalAll的等待唤醒方法,提供了等待唤醒的条件锁的同步实现;
ConditionObject为并发编程中的同步提供了等待唤醒的方法,可以在不满足条件的时候挂起线程等待,直到满足某个条件的时候再唤醒线程;使用方式如下:
ReentrantLock reentrantLock = new ReentrantLock(true);
Condition condition = reentrantLock.newCondition();
new Thread(() -> {
reentrantLock.lock();
System.out.println(Thread.currentThread().getName()+"get lock successfully");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" get signal successfully");
reentrantLock.unlock();
},"first thread ").start();
new Thread(() -> {
reentrantLock.lock();
System.out.println(Thread.currentThread().getName()+"get lock successfully");
condition.signalAll();
condition.signal();
System.out.println(Thread.currentThread().getName()+" get signal successfully");
reentrantLock.unlock();
},"second thread ").start();
在AQS同步器中可以定义多个Condition,只需要多次lock.newCondition(),每次都会返回新的ConditionObject对象,在每一个ConditionObject中都会维护一个条件等待队列来维护条件等待的线程,所以在一个同步器中可以维护多个条件等待队列,她们等待唤醒的条件是不一样的;
看一下ConditionObject维护的条件等待队列的两个重要属性
//头节点 private transient Node firstWaiter; //尾节点 private transient Node lastWaiter;
Condition的等待唤醒必须要在同步块中使用,因为线程需要在同步等待队列和条件等待队列来回切换,需要涉及释放锁;
条件等待队列:
使用的是上面说过的AQS的内部类Node类(和Lock中同步等待队列使用同一个节点,区别在于这是个单向链表,并且接连下个节点的属性也不一样),其中有两个重要的属性
Node nextWaiter:这个属性是记录下一个节点的属性,volatile int waitStatus:节点状态;
下面直接看ConditionObject的源码:
await()方法:
public final void await() throws InterruptedException {
//判断线程是否中断
if (Thread.interrupted())
throw new InterruptedException();
//加入条件队列中(队尾)
Node node = addConditionWaiter();
//释放当前线程占有的锁
int savedState = fullyRelease(node);
int interruptMode = 0;
//这一步自旋的判断条件是:是否为在同步等待队列中或者是否在条件等待队列中,如果不在同步等待队列或者在条件等待队列中则返回false;这个自旋的用处在于当调用singal()或者sinalAll方法后,被唤醒的线程会被移除条件等待队列,加入到同步等待队列中;如果返回false,则线程就会挂起等待
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
//判断线程是否中断,如果没有中断则返回0;
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//acquireQueued(node, savedState)这个方法是尝试获取锁,如果没有获取到则进行阻塞,返回线程中断状态
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//如果当前节点有后置节点,则将条件等待队列中//waitStatus !=Node.CONDITION的节点移除;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
//如果当前线程有中断标记THROW_IE(这个状态表示等待和等待之前被中断;)的,则就会报异常;
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
await()方法主要做的是释放锁,并加入条件等待队列中;并进行阻塞
signal()方法:
public final void signal() {
//如果当前线程不是独占线程,则抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//条件等待队列的头
Node first = firstWaiter;
if (first != null)
//把条件队列的头节点移除条件等待队列,如果然后加入的同步等待队列
doSignal(first);
}
final boolean transferForSignal(Node node) {
//修改节点的状态为初始状态0
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//这个方法之前看过,加入到同步等待队列中
Node p = enq(node);
int ws = p.waitStatus;
//修改节点状态为可获取锁状态,并使线程挂起等待
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}



