前言
在说ReentrantLock之前,我们先说说并发吧。
在JDK1.5之前,并发处理常用的关键字synchronized。使用synchronized关键字,锁的获取和释放是隐式的,synchronized主要通过系统的monitorenter指令实现的。
那时候synchronized可以称为重量级锁,执行效率不是很高。
而Doug Lea编写的util.concurrent 包被纳入JSR-166标准。这里面就包含了ReentrantLock。
ReentrantLock为编写并发提供了更多选择。
使用
ReentrantLock的通常用法如下:
1 2 3 4 5 6 7 8 9 10 11 12
| public class X { private final ReentrantLock lock = new ReentrantLock(); public void m() { lock.lock(); try { } finally { lock.unlock(); } } }
|
原理
ReentrantLock主要是通过AbstractQueuedSynchronizer实现的,是一个重入锁,即一个线程加锁后仍然可以获得锁,不会出现自己阻塞自己的情况。
UML图
我们看一下它们的UML图。
可以看到ReentrantLock实现了Lock接口。
锁类型
ReentrantLock的两种锁类型,公平锁和非公平锁。
源码分析
我们先来看下ReentrantLock的构造方法。
1 2 3 4 5 6
| public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
|
可以看到默认无参构造方法为非公平锁实现。如果想定义公平锁实现,可以传入true来控制。
它的lock方法:
1 2 3 4 5 6 7 8
| public void lock() { sync.acquire(1); } public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
|
公平锁和非公平锁各有自己的实现方式。我们来看下他们的tryAcquire方法。
非公平锁源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } 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) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
|
可以看到,非公平锁首先判断AQS(AbstractQueuedSynchronizer)中的state是否为0,0表示没有线程持有该锁,当前线程就尝试获取锁。
如果不是0,那在判断是不是当前线程持有该锁,如果是,就会增加state,改变state状态。(因此ReentranLock支持重入)。
公平锁源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; @ReservedStackAccess 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; } }
|
1 2 3 4 5 6 7
| public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
|
公平锁的tryAcquire方法,可以看到,相比非公平锁,多了hasQueuedPredecessors方法,这个方法是判断队列中是否有其他线程,如果没有,线程才会尝试获取锁,如果有,会先把锁分配给队列的线程,因此称为公平锁。
这儿可以看到,非公平锁的效率比公平锁要高。
这是tryAcquire方法,如果尝试获取锁失败了呢?
那就会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法啦。
我们先来看一下addWaiter方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| private Node addWaiter(Node mode) { Node node = new Node(mode);
for (;;) { Node oldTail = tail; if (oldTail != null) { node.setPrevRelaxed(oldTail); if (compareAndSetTail(oldTail, node)) { oldTail.next = node; return node; } } else { initializeSyncQueue(); } } }
|
可以看到,这个方法会把线程添加到队列尾,同时,for(;;)循环保证添加成功,直到return出去。
添加后,调用acquireQueued方法,这个方法为挂起等待线程。
看下该方法源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| final boolean acquireQueued(final Node node, int arg) { try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } catch (Throwable t) { cancelAcquire(node); throw t; } } private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }
|
可以看到,如果节点为头节点,就尝试获取一次锁,如果成功,就返回。
否则判断该线程是否需要挂起,如果需要的化就调用parkAndCheckInterrupt挂起。
调用LockSupport.park方法挂起线程,直到被唤醒。
selfInterrupt方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| static void selfInterrupt() { Thread.currentThread().interrupt(); } public void interrupt() { if (this != Thread.currentThread()) checkAccess(); synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { interrupt0(); b.interrupt(this); return; } } interrupt0(); }
|
调用interrupt方法,中断正在执行的线程(如果不是当前线程的话)。
释放锁unlock方法:
公平锁和非公平锁释放锁的方法是一样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public void unlock() { sync.release(1); } public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; 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; }
|
可以看到首先会判断当前线程是否是获得锁的线程,如果是重入锁需要将state减完才算是完全释放锁。
释放后调用unparkSuccessor唤起挂起线程。
总结
- 非公平锁的效率是比公平锁要高的。
- ReentranLock支持重入,因为增加了对自身线程的处理,通过state可以控制。
- 解锁操作应放到finally块里,避免使用锁时出现资源无法释放的问题。