您的位置:首页 > 编程语言 > Java开发

Java并发之ReentrantLock原理详解

2018-02-12 12:13 597 查看

一、ReentrantLock是什么?

    ReentrantLock是Lock接口的默认实现,是一种独占锁。相对synchronized而言,ReentrantLock提供了更多的操作方式以及更细粒度的加锁方式。
    主要特性:

    (1)可重入。ReentrantLock是可重入锁,因为它会记录之前获得锁线程对象,保存在exclusiveOwenerThread变量中,当一个线程要获取锁时,会先判断当前线程是不是已经获取锁的线程。synchronized也是可重入锁。

    (2)可中断。ReentrantLock是可中断锁,它提供了lockInterruptibly这种可中断的加锁方式,可以有效的避免线程之间因为互相持续占有资源而导致阻塞。synchronized无法实现可中断。

    (3)公平锁与非公平锁可选。ReentrantLock默认是公平锁,但是也可以通过构造方法选择非公平锁。公平锁是指当多个线程尝试获取同一个锁时,获取锁的顺序按照到达的时间顺序排序。

二、ReentrantLock的底层原理。

ReentrantLock是继承了队列同步器AQS,关于AQS的原理,我之前写过博客,链接为点击打开链接
我们先要了解一下ReentrantLock的两个构造方法。    public ReentrantLock() {
sync = new NonfairSync();//默认为不公平锁
}

public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();//若为true,则是公平锁
}ReentrantLock通过构造器参数选择到底是公平模式还是非公平模式。
再通过一张类关系图看一下NonfairSync和FairSync这两个内部类的来头。



可以看到NonfairSync和FairSync都是继承了Sync这个抽象类,而Sync则继承了AQS。Sync、NonfairSync、FairSync都是ReentrantLock的静态内部类,ReentrantLock的许多方法都是Sync类代为实现。
再看一下关键的最为关键的lock方法。public void lock() {
    sync.lock();//sync可能是NonfairSync或者FairSync
}很简单的一个方法实现,sync代为实现lock的逻辑,而sync是Sync的实例,对于ReentrantLock来说,它的代码不需要知道Sync到底是NonfariSync还是FairSync,在运行时才会知道。这就是设计模式中的策略模式。
1、接下来先来看NonfairSync的lock方法。final void lock() {
    if (compareAndSetState(0, 1)) //先通过CAS操作尝试获取锁,即把state从0变为1
    setExclusiveOwnerThread(Thread.currentThread()); //若获取锁成功,则设置独占的线程为当前线程
else
acquire(1); //若无法通过上述操作获得锁,则要通过AQS的acquire操作来再次获取锁,若还未成功则进入等待队列
}acquire方法在AQS中已经提供实现不需要重写。关于AQS的讲解在这个链接的博客。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
关键是需要NonfairSync自己实现的tryAcquire。protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);//调用了父类的方法
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();//先获取当前线程对象
int c = getState(); //获取state的值
if (c == 0) { //若state为0,则可以通过cas方式获取锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current); //将当前线程设置为独占线程
return true;
}
}
else if (current == getExclusiveOwnerThread()) { //若state=1,则先看当前线程是否和已经独占的线程相等
int nextc = c + acquires; //如果是已经获取锁的线程,则修改state
if (nextc < 0) // overflow 判断state的合法性
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
2、现在我们来看一下公平锁下的acquire方法。
final void lock() {
acquire(1);
}
很简单,不会先尝试抢占锁,直接调用acquire方法,这就是公平锁的公平之处。acquire方法就不说了,在AQS中已经写好了,Sync也没有重写这个方法,直接看acquire中的tryAcquire方法。protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&//在这里要判断队列中是否有等待线程,若有就不能尝试获得锁,因为是公平锁,必须排队
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}这个公平锁的tryAcquire方法与非公平锁的tryAcquire方法只差了一点点,那就是当没有线程占有锁时,不会在队列中有等待线程存在的情况下去尝试抢占锁,这也是保证公平的必要条件,就是乖乖排队。
3、释放锁的方法unlock。public void unlock() {
sync.release(1);
}也只是简单的调用了一下Sync的release方法,释放方法不存在公平与非公平的区别,这个方法就是AQS中已经实现的方法。public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}主要说一下tryRelease方法,该方法也不存在公平与非公平的区别。protected final boolean tryRelease(int releases) {
int c = getState() - releases;//state值变化
if (Thread.currentThread() != getExclusiveOwnerThread())//若当前的线程与独占线程不是同一个,则抛出异常
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {//若释放锁后,没有线程占用锁了
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息