ReentrantLock 是一个可重入的锁,用于线程之间的同步。
采用独占锁方式,即一个线程获得锁,其它线程需等待锁的释放。
ReentrantLock与内置锁synchronized相比,多了:
1.lockInterruptibly() 获取锁的过程中 可以响应中断并返回假
2.
获取锁的过程中,可以响应中断并返回假、支持超时检测并返回假,tryLock(long timeout, TimeUnit unit)
3.可以不在一个代码中,但要保证锁的释放
jdk1.5 中synchronized 性能没有 ReentrantLock好,jdk1.6中就差不多了。
class X { private final ReentrantLock lock = new ReentrantLock(); public void m() { lock.lock(); // 当前线程如果能够获得锁,则继续执行,否则阻塞,等待其它线程释放锁并得到锁 try { // ... method body } finally { lock.unlock();//已获取锁,执行完逻辑,释放锁,注意,需要确保锁的释放,否则其它线程会一直阻塞中。 } } }
方法:
int |
getHoldCount() 查询当前线程保持此锁定的次数。 |
protected Thread |
getOwner() 返回目前拥有此锁定的线程,如果此锁定不被任何线程拥有,则返回 null。 |
protected Collection<Thread> |
getQueuedThreads() 返回一个 collection,它包含可能正等待获取此锁定的线程。 |
int |
getQueueLength() 返回正等待获取此锁定的线程估计数。 |
protected Collection<Thread> |
getWaitingThreads(Condition condition) 返回一个 collection,它包含可能正在等待与此锁定相关给定条件的那些线程。 |
int |
getWaitQueueLength(Condition condition) 返回等待与此锁定相关的给定条件的线程估计数。 |
boolean |
hasQueuedThread(Thread thread) 查询给定线程是否正在等待获取此锁定。 |
boolean |
hasQueuedThreads() 查询是否有些线程正在等待获取此锁定。 |
boolean |
hasWaiters(Condition condition) 查询是否有些线程正在等待与此锁定有关的给定条件。 |
boolean |
isFair() 如果此锁定的公平设置为 true,则返回 true。 |
boolean |
isHeldByCurrentThread() 查询当前线程是否保持此锁定。 |
boolean |
isLocked() 查询此锁定是否由任意线程保持。 |
void |
lock() 获取锁定。 |
void |
lockInterruptibly() 如果当前线程未被 中断 ,则获取锁定。 |
Condition |
newCondition() 返回用来与此 Lock 实例一起使用的 Condition 实例。 |
String |
toString() 返回标识此锁定及其锁定状态的字符串。 |
boolean |
tryLock() 仅在调用时锁定未被另一个线程保持的情况下,才获取该锁定。 |
boolean |
tryLock(long timeout, TimeUnit unit) 如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被 中断 ,则获取该锁定。 |
void |
unlock() 试图释放此锁定。 |
具体实现是通过类AbstractQueuedSynchronizer 来实现。
AbstractQueuedSynchronizer 是多线程锁实现的基类,采用先进先出线程队列,队列中存有想要获得锁的线程,先入队列的线程先获得锁,当它释放锁之后,后进入队列的再获得锁,以此类推。
内部成员变量:state ,定义当前锁定次数。0 未被锁定,一个线程可以锁定N次。 当一个线程已经有锁时,再调用锁定时,不需要争抢锁,state+=1,记录锁的次数。释放锁时必须释放N次才能真正释放锁。
由Node对象组成一个等待获得锁的链表。
基本原理: 当线程想要获取锁,如果state ==0则锁未被占用,可以获得锁,如果state>0则锁被占用,将当前线程生成Node对象放到链表尾部,调用Unsafe.park阻塞当前线程。 当已获得锁的线程释放锁,从链表头部找需要锁的线程,调用Unsafe.unpark 恢复线程 。
类图:
公平锁:FairSync类实现,按照线程请求锁的先后顺序时获得锁,即使请求瞬间锁未被占用,但队列中有其它线程等待锁,则当前线程也不会去争抢锁。
非公平锁:NoFairSys类实现,当前线程要获得锁,如果锁未被获得(可能上一个持有锁的线程已释放锁,队列中阻塞线程已恢复,处于就绪状态,但还未获得锁时),当前线程会与其它线程争抢锁,可能会抢到锁,获得执行程序机会。 如果当前线程执行时间较短,执行完成,锁释放后,被阻塞恢复的线程才来获得锁,这相当于两个线程之间又多执行了一条线程,性能比公平锁好一些。
非公平锁Lock时序图:
非公平锁Lock说明:
final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }compareAndSetState ,是AbstractQueuedSynchronizer的方法,用CAS(compareAndSwap)方式设定state, compareAndSetState(0,1) 当state==0(没有线程占用锁,有可能持有锁的线程刚释放锁)时,设为1,这时可能有其它线程(队列中被阻塞线程已被恢复(unpark)或刚执行到此处的其它线程)也在争抢锁。
如果返回真,则表示当前线程抢占了锁,则其它线程进阻塞。
如果返回假:则调用 acquire方法。
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
tryAcquire 试图再去获取锁,tryAcquire中调用 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()) {//如果当线程已经获得了锁,再次锁定时计器state+1,不需要在争抢锁了,因为之前已持有锁 int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true;//返回真,表示当前线程获得销 } return false;//抢锁失败 }
如果tryAcquire方法返回假,即没有抢到锁,则执行 addWaiter方法,将线程存入链表尾部。
private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // 把当前线程存入链表尾部 Node pred = tail;//为什么不直接使用共享变量tail??可能volatile型变量每次读取直接访问内存,可以通过赋值引用,在线程缓存中新生成一个引用,每次访问这个引用,速度快。 共享变量可能随时改变,比如先后调用二次成员变量上的方法,则有可能在方法之间,成员变量对象已被其它线程改变了。
if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) {//给共享成员变量赋值使用原子方法 pred.next = node;//线程安全??上一行最多只有一条线程会返回真,即本行代码最多只能被一条线程执行,是线程安全的。即使执行本行前,线程被调度挂起,其它线程执行,改变了TAIL,线程恢复时,再执行,由于 局部变量pred是之前tail的引用值,不会随当前tail值变化,所以是正确的。 return node; } } enq(node); return node; }
当前线程加入队列中之后,进入 acquireQueued方法
final boolean acquireQueued(final Node node, int arg) { try { boolean interrupted = false; for (;;) {//死循环,直至线程获得独占锁,包括(刚进入的线程或阻塞之后恢复的线程) final Node p = node.predecessor();//得到当前线程的前一个节点 if (p == head && tryAcquire(arg)) {//如果前一节点为HEAD,则试着获取锁,得到锁时,把自己置为HEAD setHead(node);//线程安全?? 只有一条线程能获得锁,执本本行代码 p.next = null; // help GC return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())//parkAndCheckInterrupt中调用 LockSupport.park(this),线程恢复后会从下一行代码继续执行。 interrupted = true; } } catch (RuntimeException ex) { cancelAcquire(node); throw ex; } }
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int s = pred.waitStatus; if (s < 0) /* * This node has already set status asking a release * to signal it, so it can safely park */ return true; if (s > 0) { /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0);//链表中去除已经被取消的线程 pred.next = node; } else /* * 设定前一个节的waitStatus=Node.SIGNAL,表示本节点阻塞需要unpark,unpark的任务交给前一个节点的release完成。 */ compareAndSetWaitStatus(pred, 0, Node.SIGNAL); return false; }
锁的释放:
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0)//h.waitStatus=0没有后续节点?? unparkSuccessor(h);//找到head之后的需要解锁的线程,调用 LockSupport.unpark(s.thread);进行解锁 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) {//锁定次数为0,是真正释放锁,否则只是释放了一层锁,当前线程还是持有锁 free = true; setExclusiveOwnerThread(null); } setState(c); return free; }