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;
}