# 1.ReentrantLock特点
- 可中断
private static ReentrantLock lock=new ReentrantLock();
public static void main(String[] args) {
Thread thread = new Thread(() -> {
//如果没有竞争此方法会获得lock锁,如果有竞争,就进入阻塞队列,
//可以被其他线程用interrupt方法打断
try {
System.out.println("尝试获取锁");
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("没有获取到锁");
return;
}
try {
System.out.println("获取到了锁");
} finally {
lock.unlock();
;
}
});
lock.lock();
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
在ReentrantLock中,lockInterruptibly()方法是获得锁,但是它是可以打断的,什么意思呢,就是在上面的例子中,肯定是主线程获取到了锁,然后thread线程开始执行,这个时候因为这俩个线程公用的是同一把锁,因此thread线程肯定是获取不到锁了,1秒后,调用thread线程的interrupt方法,意思就是来打断,这个时候thread感知到自己被打断了,就抛出异常,进入catch块中。lockInterruptibly()是可打断活的锁,而lock()方法获得锁是不能打断的
执行结果
- 可以设置超时时间:ReentrantLock的tryLock()的方法是尝试获取锁,里面还可以通过设置参数进行超时等待,例如lock.tryLock(1, TimeUnit.SECONDS)代表尝试等待1秒后获取锁。
- 可以设置为公平锁:ReentrantLock默认是不公平锁,通过构造方法设置是否公平。
- 支持多个条件变量
- 与synchronized一样,都支持可重入
# 2.基本语法
// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}
# 3.ReentrantLock原理
# 3.1非公平锁实现原理
- 加锁解锁流程
- 先从构造器开始看,默认为非公平锁实现
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
可以看到同步器对象,默认用的是NonfairSync();非公平锁
- 当调用lock方法时:
/**
* Acquires the lock.
*
* <p>Acquires the lock if it is not held by another thread and returns
* immediately, setting the lock hold count to one.
*
* <p>If the current thread already holds the lock then the hold
* count is incremented by one and the method returns immediately.
*
* <p>If the lock is held by another thread then the
* current thread becomes disabled for thread scheduling
* purposes and lies dormant until the lock has been acquired,
* at which time the lock hold count is set to one.
*/
public void lock() {
sync.lock();
}
调用的是同步器的lock()方法
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
当没有竞争时,就直接将它的exclusiveOwnerThread设置为当前线程,并将state设置为1
# 加锁失败流程
第一个竞争出现时,
这时候肯定就进入acquire(1)方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
Thread-1 执行了
- CAS 尝试将 state 由 0 改为 1,结果失败
- 进入 tryAcquire 逻辑,这时 state 已经是1,结果仍然失败
- 接下来进入 addWaiter 逻辑, 构造 Node 队列
- 图中黄色三角表示该 Node 的 waitStatus 状态,其中 0 为默认正常状态
- Node 的创建是懒惰的
- 首次会创建俩个节点,其中第一个 Node 称为 Dummy(哑元)或哨兵,用来占位,并不关联线程,第二个节点就是Thread-1节点,双向链表相互关联
private Node addWaiter(Node mode) {
//给当前线程创建一个节点
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//创建一个
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
当前线程进入 acquireQueued 逻辑
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取当前Thread-1的前驱节点也就是刚才那个站位节点
final Node p = node.predecessor();
//如果你这个节点是站位节点,会在做一次尝试。
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//这个方法就是当尝试失败的时候是否应该park阻塞住
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞
- 如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
- 进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,为什么设置为-1,在这里面设置为-1就代表你有义务去唤醒下一个节点,这次返回 false
4. shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued ,再次 tryAcquire 尝试获取锁,当然这时state 仍为 1,失败
5. 当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回true
6. 进入 parkAndCheckInterrupt, Thread-1 park(灰色表示)
再次有多个线程经历上述过程竞争失败,变成这个样子
# 释放锁流程
unlock方法其实就是
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;
}
# 释放锁成功流程
Thread-0 释放锁,进入 tryRelease 流程,如果成功
设置 exclusiveOwnerThread 为 null
state = 0
当前队列不为 null,并且 head 的 waitStatus = -1,进入 unparkSuccessor 流程
找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中即为 Thread-1
回到 Thread-1 的 acquireQueued 流程
如果加锁成功,会设置
- exclusiveOwnerThread为Thread-1,state=1
- head指向刚刚Thread-1所在的node,该Node情况Thread
- 原本的heand因为从链表断开,而可被垃圾回收。
# 释放锁失败
如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了
如果不巧又被 Thread-4 占了先
- Thread-4 被设置为 exclusiveOwnerThread,state = 1
- Thread-1 再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞
# 总结
ReentranLock的加锁流程,当线程1进来时,没有线程竞争的时候,他就会将exclusiveOwnerThread设置成自己,并将state设置1,就当下一次有线程进来的时候,告诉对方,这个锁现在被人持有了,然后线程2进来时,发现这个锁正在被别人持有,会再次尝试一下,如果还是失败,会通过addWaiter创建一个双向链表,首次创建俩个节点,第一个是站位节点,第二个是线程2的节点,这个时候这俩个节点状态都为0。正常状态,创建成功之后,会进入acquireQueued方法死循环不断尝试获得锁,尝试几次以后都失败了,进入park阻塞了,进入 shouldParkAfterFailedAcquire将第一个节点waitState改为-1.就代表这个节点有义务去唤醒下一个节点。然后如果有多个线程都来获取锁,都失败了,会一个一个排在后面,并且都将前一个节点waitestate改为-1.
解锁流程:当一个线程调用unpark释放锁时,将exclusiveOwnerThread设置为null,state=0当队列不为空时,且状态为-1时就代表这个队列里面有需要唤醒的线程。这个时候找到队列里还在排队的最近的线程(Thread1)。恢复运行。这个时候exclusiveOwnerThread就变成了Thread1,state=1.队列中head节点就指向了Thread1节点,刚刚那个节点就断开了,被回收掉了。
如果刚释放锁恰巧来了新的线程(Thread8),这里也就是非公平锁的体现。有可能这个Thread8直接就占有了锁,exclusiveOwnerThread设置为Thread8,state1,这个时候Thread1再次进入acquireQueued,获取锁失败,进入阻塞状态