读本篇前,一定要确保已经读过本公众号的AQS讲解。
我们知道实现一把锁要有如下几个逻辑
我们在讲解AQS的时候说过AQS基本负责了实现锁的全部逻辑,唯独线程抢锁和线程释放锁的逻辑是交给子类来实现了,而ReentrantLock作为最常用的独占锁,其内部就是包含了AQS的子类实现了线程抢锁和释放锁的逻辑。
我们在使用ReentrantLock的时候一般只会使用如下方法:
ReentrantLock lock=new ReentrantLock(); lock.lock(); lock.unlock(); lock.tryLock(); Condition condition=lock.newCondition(); condition.await(); condition.signal(); condition.signalAll();
如果我们自己来实现一个锁,那么如何设计呢?
根据AQS的逻辑,我们写一个子类sync,这个类一定会调用父类的acquire方法进行上锁,同时重写tryAcquire方法实现自己抢锁逻辑,也一定会调用release方法进行解锁,同时重写tryRelease方法实现释放锁逻辑。
图片
那么ReentrantLock是怎么实现的呢?
ReentrantLock的实现的类架构如下,ReentrantLock对外提供作为一把锁应该具备的api,比如lock加锁,unlock解锁等等,而它内部真正的实现是通过静态内部类sync实现,sync是AQS的子类,是真正的锁,因为这把锁需要支持公平和非公平的特性,所以sync又有两个子类FairSync和NonfairSync分别实现公平锁和非公平锁。
图片
因为是否公平说的是抢锁的时候是否公平,那两个子类就要在上锁方法acquire的调用和抢锁方法tryAcquire的重写上做文章。
公平锁做了什么文章?
static final class FairSync extends Sync { final void lock() { acquire(1); } 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; } }
公平锁比较简单,直接调用了父级类AQS的acquire方法,因为AQS的锁默认就是公平的排队策略。
重写tryAcquire方法的逻辑为:
公平就公平在老老实实排队。
非公平锁做了什么文章?
static final class NonfairSync extends Sync { final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } //nonfairTryAcquire代码在父类sync里面 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); 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; }
非公平锁也很简单,没有直接调用了父级类AQS的acquire方法,而是先通过cas抢锁,它不管等待队列中有没有其他线程在排队,直接抢锁,这就体现了不公平。
它重写tryAcquire方法的逻辑为:
公平锁和非公平分别重写了tryAcquire方法,来满足公平和非公平的特性。那么tryAcquire方法也是需要子类重写的,因为它和是否公平无关,因此tryAcquire方法被抽象到sync类中重写。
sync类中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; }
释放锁的逻辑如下:
释放锁往往和抢锁逻辑是对应的,每个子类抢锁逻辑不同的话,释放锁的逻辑也会对应不同。
接下来我们通过ReentrantLock的使用看下它的源码实现
class X { private final ReentrantLock lock = new ReentrantLock(); Condition condition1=lock.newCondition(); Condition condition2=lock.newCondition(); public void m() { lock.lock(); try { if(条件1){ condition1.await(); } if(条件2){ condition2.await(); } } catch (InterruptedException e) { } finally { condition1.signal(); condition2.signal(); lock.unlock(); } } }
ReentrantLock类public void lock() { sync.lock(); }
NonfairSync 类中 final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
FairSync 类中 final void lock() { acquire(1); }
公平锁和非公平锁中都实现了lock方法,公平锁直接调用AQS的acquire,而非公平锁先抢锁,抢不到锁再调用AQS的acquire方法进行上锁
进入acquire方法后的逻辑我们就都知道了。
public void unlock() { sync.release(1);}
unlock方法内直接调用了AQS的Release方法进行解锁的逻辑,进入release方法后逻辑我们都已经知道了,这里不再往下跟。
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); }
tryLock方法直接调用sync的tryAcquireNanos方法,看过AQS的应该知道tryAcquireNanos这个方法是父类AQS的方法,这个方法和AQS中的四个核心方法中的Acquire方法一样都是上锁的方法,无非是上锁的那几个步骤,调用tryAcquire方法尝试抢锁,抢不到锁就会进入doAcquireNanos方法。
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout); }
doAcquireNanos这个方法做的其实就是入队,阻塞等一系列上锁操作,逻辑和Acquire方法中差不多,但是有两点不同:
看下下面的代码
private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (nanosTimeout <= 0L) return false; final long deadline = System.nanoTime() + nanosTimeout; final Node node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return true; } nanosTimeout = deadline - System.nanoTime(); if (nanosTimeout <= 0L) return false; if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold) LockSupport.parkNanos(this, nanosTimeout); if (Thread.interrupted()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
这里的阻塞不再是LockSupport类的park方法,而是parkNanos方法,这个方法支持指定时长的阻塞,AQS正是利用这个方法实现阻塞指定时长,当自动唤醒后,循环中会判断是否超过设定时长,如果超过直接返回false跳出循环。
在阻塞期间,如果线程被中断,就会抛出异常,同样会跳出循环,外面可以通过捕获这个异常达到中断阻塞的目的。
可见ReentrantLock其实啥也没做,其tryLock方法完全是依赖AQS实现。
在AQS那篇我们说过Condition是AQS中的条件队列,可以按条件将一批线程由不可唤醒变为可唤醒。
ReentrantLock类 public Condition newCondition() { return sync.newCondition(); }
sync静态内部类final ConditionObject newCondition() { return new ConditionObject();}
sync提供了创建Condition对象的方法,意味着ReentrantLock也拥有Condition的能力。
我们下面说的ReentrantLock其实就是说AQS,因为它的同步实现主要在AQS里面。
synchronized不需要手动释放锁,ReentrantLock需要手动释放锁,需要考虑异常对释放锁的影响避免异常导致线程一直持有锁。
以下是两个锁的使用方式
class X { private final ReentrantLock lock = new ReentrantLock(); Condition condition1=lock.newCondition(); Condition condition2=lock.newCondition(); public void m() { lock.lock(); try { if(1==2){ condition1.await(); } if(1==3){ condition2.await(); } } catch (InterruptedException e) { } finally { condition1.signal(); condition2.signal(); lock.unlock(); } } }
class X { private final testtest sync=new testtest();; public void m() throws InterruptedException { synchronized(sync){ if(1==2){ sync.wait(); } sync.notify(); sync.notifyAll(); } } }
对比代码及特性说明:
本文链接:http://www.28at.com/showinfo-26-13582-0.html面试中如何答好:ReentrantLock
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: Java中的Volatile到底是什么?