相对synchronized不同点
- 可以设置超时时间(阻塞多久获取不到锁就放弃)
- 可以中断(阻塞状态可以被中断放弃)
- 可以设置多个条件变量(wait notify相当于是一个,reentrantlock可以设置多个环境变量)
- 可以设置公平锁(从阻塞中唤醒竞争时可以时公平的(谁先请求,谁先获取锁))
使用举例
// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}
可中断
获取锁的阻塞状态可以被中断掉
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
System.out.println("锁获取中断,退出");
e.printStackTrace();
return;
}
可超时
//获取是否加锁成功
boolean isLock = lock.tryLock();
//设置超时时间
lock.tryLock(1, TimeUnit.SECONDS);
条件变量
类似synchronized的wait 和 notify ,但是更自由,不用唤醒所有。可以按condition唤醒
static Condition condition1 = lock.newCondition();
//等待
condition1.await();
condition1.await(1,TimeUnit.SECONDS);
//通知
condition1.notify();
condition1.notifyAll();
设置公平锁
唤醒阻塞中的线程可以是公平的,先进获取
static ReentrantLock lock = new ReentrantLock(true);
原理解析
类结构图如下:可以看到是基于AQS实现的
加锁
如果没有竞争:
exclusiveOwnerThread指向加锁线程
如果有竞争,也急速cas修该state失败,则进入acqure函数:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
总体流程,构造Node节点,然后加入队列中(还会有好几次尝试获取锁),然后调用LockSupport.park(this);
方法阻塞当前线程。
具体分析见pdf文档。
解锁
- 调用tryRelease方法进行锁释放
- 如果释放成功,判断队列head不为空且waitState不为0,进入唤醒流程
unparkSuccessor
- 从tail往前遍历,找到最近一个需要唤醒的线程
- 调用
LockSupport.unpark(s.thread);
唤醒阻塞线程 - 解锁完之后,被唤醒的线程会在下一次循环中将自己从队列中删除(head指向自己,自己前驱的next为null)
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
可重入原理
如果获取锁的是当前线程,则state++,调用setState方法设置state
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;
}
可打断原理
不可打断,发生中断时会唤醒park线程,这里清除打断标记(不清除的话,线程就无法再次park)。然后局部变量interrupted=true
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();//清除打断标记
}
只有等线程获取到锁之后才可以感知到中断(内部变量interrupted=true时,再触发一次中断)。
static void selfInterrupt() {
// 重新产生一次中断
Thread.currentThread().interrupt();
}
可打断
被中断唤醒之后抛出中断异常,线程结束
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
公平原理
多了一步判断队列中是否有等待唤醒的线程。hasQueuedPredecessors
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;
}
}
对于非公平锁,没有队列判断,直接去竞争
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
条件变量
每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject(firstWaiter、lastWaiter两个节点)
await流程:
- 创建Node节点(和CLH队列中的Node一致),加入等待队列尾部
- 调用fullRelease释放锁
- unpark AQS 队列中的下一个节点
-
park 阻塞 Thread-0(当前线程)
signal流程:
- 取等待队列第一个Node,将Node加入AQS队列尾部