ReentrantLock介绍
我们知道ReentrantLock是juc包下的一个锁,它主要用lock方法来加锁,保证同步块同一时间只能有一个线程运行,但有比synchronized 更加丰富的功能,比如:公平和非公平(synchronized只有非公平锁),可中断响应(当获取到的锁被中断时会抛出中断异常,同时释放锁),超时等待(超过一定的时间未获得锁便不再等待)等
ReentrantLock 通过方法lock()与unlock()来进行加锁与解锁操作,与synchronized 会被JVM 自动解锁机制不同,ReentrantLock 加锁后需要手动进行解锁。为了避免程序出现异常而无法正常解锁的情况,使用ReentrantLock 必须在finally 控制块中进行解锁操作。如果通过创建Condition 对象来使线程wait,必须先执行lock.lock 方法获得锁,
方法condition.signal()相当于notify(),用于唤醒正在wait的线程
关于ReentrantLock和synchronized的性能比较:
在竞争不激烈的情况下synchronized优于ReentrantLock,因为synchronized内部对锁进行了优化,由偏向锁-轻量级锁-重量级锁对锁进行了升级,而偏向锁内部不用cas机制获得锁,不需要cas来加锁和解锁的开销,偏向锁会在markword上写入偏向的线程id,如果一直是这一个线程进入同步块则无需进行其他加锁解锁操作,偏向锁的目的是在某个线程获得锁之后,消除这个线程锁重入(CAS)的开销,看起来让这个线程得到了偏护。
但在竞争激烈的情况下,ReentrantLock的 性能优于synchronized,因为synchronized的重量级锁涉及操作系统的状态切换(用户态-内核态)非常消耗时间,因此重量级锁效率很低,而ReentrantLock采用cas是一种“无锁”思想,也即是乐观锁,CAS是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败,具体操作使用到了 unsafe 类,底层用到了本地方法 unsafe.compareAndSwapInt 比较交换方法。这种操作是 CPU 指令集操作,只有一步原子操作,速度非常快。而且 CAS 避免了请求操作系统来裁定锁问题。
源码分析
先介绍一些aqs的知识:
AQS内部用一个volatile修饰的int类型的成员变量state来控制同步状态。
- state = 0:表示没有线程正在独占共享资源的锁。
- state = 1:表示有线程正在共享资源的锁。
AQS定义两种资源共享方式:
Exclusive-独占,只有一个线程能执行,如ReentrantLock
Share-共享,多个线程可以同时执行,如Semaphore/CountDownLatch 。
AQS定义两种队列
注意:通过Node我们可以实现两个队列,⼀是通过prev和next实现CLH队列(线程同步队列,双向队列),⼆是nextWaiter实现Condition条件上的等待线程队列(单向队列),这个Condition主要⽤在ReentrantLock类中。
Condition接口的主要实现类是AQS的内部类ConditionObject
,每个Condition对象都包含一个等待队列。该队列是Condition对象实现等待/通知的关键。
AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:
isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。
ReentrantLock的加锁方法
首先看它的lock方法和Trylock方法:
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
public void lock() {
sync.lock();
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);//可中断式获取锁
}
public boolean tryLock() {
//仅当调用时没有被另一个线程持有时才获取锁。如果锁没有被另一个线程持有,则获取锁并立即返回值为 true,将锁持有计数设置为 1。即使此锁已设置为使用公平排序策略,调用 tryLock() 也会立即获取该锁(如果可用),无论其他线程当前是否正在等待该锁。
return sync.nonfairTryAcquire(1);
}
...}
trylock和lock:
1.tryLock不管拿到拿不到都直接返回;lock如果拿不到则会一直等待。
2.tryLock是可以中断的
它内部实际上是调用了sync.lock和 nonfairTryAcquire方法,
sync:这是一个抽象类, 继承了aqs:
abstract static class Sync extends AbstractQueuedSynchronizer {//继承了aqs抽象队列同步器
abstract void lock();//有公平实现和非公平实现
final boolean nonfairTryAcquire(int acquires) {//非公平获得锁
//首先查看这个对象是否已经被加锁,如果未被加锁则用cas方法试图获得锁,如果获得锁成功则将获得这个锁的线程设置为自己,如果已经被加锁则判断是否是自己加的锁 如果是自己加锁则加重入锁并修改state值,否则返回false
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);//cas方法尝试以独占的方式获取锁
return true;
}
}
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;
}
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
final Thread getOwner() {//返回获得锁的线程,如果没有线程获得锁,返回null
return getState() == 0 ? null : getExclusiveOwnerThread();//获得当前拥有独占访问权限的线程。
}
final int getHoldCount() {//返回获得锁的数量
return isHeldExclusively() ? getState() : 0;
}
}
我们来看超时获取锁的方法:
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}//尝试以独占模式获取,如果被中断则中止,如果超过给定的超时时间则失败。首先检查中断状态,然后调用至少一次tryAcquire,成功返回。否则,线程排队,可能重复阻塞和解除阻塞,调用 tryAcquire 直到成功或线程被中断或超时过去。
关于获取公平锁的方法主要是多了一个!hasQueuedPredecessors() 判断:
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
//hasQueuedPredecessors()用于查询是否有任何线程等待获取的时间比当前线程长。
}
}
ReentrantLock的condition
再看ReentrantLock内部的Condition:
Condition 将对象监视器方法(wait、notify 和 notifyAll)分解为不同的对象,通过将它们与任意 Lock 实现的使用结合起来,使每个对象具有多个等待集的效果。 Lock 代替了同步方法和语句的使用,Condition 代替了 Object 监视器方法的使用
public Condition newCondition() {
return sync.newCondition();
}
// sync.newCondition():
final ConditionObject newCondition() {
return new ConditionObject();
}
ConditionObject:是aqs类下面的一个类:
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
private Node addConditionWaiter() {
//加入一个结点到等待队列中
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;//如果队列为空则将首结点设置为该新结点
else
t.nextWaiter = node;//否则插入到尾结点后面
lastWaiter = node;//设置新的尾结点,类似尾插法
return node;
}
await方法:
使用了 LockSupport.park(this)用来挂起线程
LockSupport:用于创建锁和其他同步类的基本线程阻塞原语。
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
//sOnSyncQueue:如果一个节点(始终是最初放置在条件队列中的节点)现在正在等待重新获取同步队列,则返回 true
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
今天的源码分析就到这啦