什么是ReentrantLock
ReentrantLock是继承Lock接口的一个在并发编程中常用的类,和synchronize一样,都是防止在并发场景下发生线程同步的问题。
使用方法
synchronize 加锁方法和 ReentrantLock 方法比较
写一个测试类,循环10次开辟10个线程,每个线程循环1000次,理论上total的出的结果是 10 * 10000 = 100000
private static int total = 0;
public static void main(String[] args) throws InterruptedException {
lockTest();
}
public static void lockTest() throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
total++;
}
}).start();
}
Thread.sleep(5000);
System.out.println(total);
}
可是输出结果却和理想的不一样,这就是发生了线程同步问题,以往解决这个问题,第一时间想到的是使用synchronize加锁。 现在将方法 lockTest 稍作修改:
public static void lockTest() throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
synchronized (Object.class) {
total++;
}
}
}).start();
}
Thread.sleep(5000);
System.out.println(total);
}
此时再输出 total 的值,就是 10 * 10000 = 100000 次。
除了使用synchronize还可以使用 ReentrantLock 来实现加锁的目的。 ReentrantLock相比 synchronize 是显性加锁方式,需要手动加锁和释放锁。 要使用 ReentrantLock 需要先创建一个 ReentrantLock 对象,使用ReentrantLock的lock()开启加锁,unlock()方法释放锁,在这两个方法中写需要加锁的代码即可。
private static int total = 0;
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
lockTest();
}
public static void lockTest() throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
lock.lock();
total++;
lock.unlock();
}
}).start();
}
Thread.sleep(5000);
System.out.println(total);
}
这样 lockTest() 方法打印出的 total 值也是 10000.
ReentrantLock 实现锁的方法是实现了AQS的框架。
AQS
AQS定义了一套多线程访问共享资源的同步框架,是一个依赖状态(State)的同步器。
三大核心原理
- 自旋
- LockSupport(可以阻塞线程和唤醒线程的工具)
- CAS 在 ReentrantLock 中还使用了双向链表,如果一个线程没有获取到锁,就回将这个线程放入队列中,ReentrantLock 类中有两个属性: head 指向链表头, tail 指向链表尾。
链表节点对象 Node 中几个属性:
- prev:指向前一个Node节点
- next:指向后一个Node节点
- thread:记录当前线程
- waitStatus:节点生命状态
- CANCELLED = 1 代表出现异常,中断引起的,需要被废弃结束
- SIGNAL = -1 可被唤醒的节点
- CONDITION = -2 条件等待
- PROPAGATE = -3 传播
- 初始状态 = 0
ReentrantLock.lock() 方法
ReentrantLock 中有一个 Sync 的内部抽象类,继承了 AbstractQueuedSynchronizer(AQS)
Sync 中有 lock()抽象方法,实现这个方法的有两个内部类 FairSync(公平锁实现)和 NofairSync(非公平锁实现)。
公平锁实现的lock() 方法
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt(); // 中断当前线程的便捷方法。
}
tryAcquire()尝试获取锁,如获取失败返回 false ,继续走 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法,addWaiter(Node.EXCLUSIVE), arg)这个方法创建排队的Node节点。
acquireQueued()方法,获取已在队列中的线程。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 进行自旋
for (;;) {
final Node p = node.predecessor(); // 获取当前线程的上一个节点指向
// 首先再次尝试在链表头的节点获取锁,这样操作的原因是当代码走到这里的时候,
// 可能上一个线程已经释放了锁资源,这里可以直接在获取锁资源,也可以帮助GC
if (p == head && tryAcquire(arg)) {
// p == head 判断当前线程的上一个节点是不是链表头
// 如果是链表头,则进行获取锁,获取成功后,将 head 指向当前 Node 节点。
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted; // 返回false 当前线程无需阻塞。
}
// 当 head 不是指向 p 或 获取锁失败后
// shouldParkAfterFailedAcquire()检查和更新未能获取的节点的状态。
// 如果线程应该阻塞,则返回 true。
// 这是所有获取循环中的主要信号控制。
// parkAndCheckInterrupt()阻塞当前线程。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
shouldParkAfterFailedAcquire()方法:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// ws:当前节点前一个节点的节点状态
int ws = pred.waitStatus;
// 如果 ws = -1 说明当先线程 可被唤醒
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 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 must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
// 将 pred 节点的状态设置为 -1 在下一次自旋中,就可返回true
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
公平锁实现的unlock() 方法
// 如果当前线程是此锁的持有者,则持有计数递减。
// 如果保持计数现在为零,则锁定被释放。
// 如果当前线程不是此锁的持有者,则抛出IllegalMonitorStateException 。
public void unlock() {
// 释放当前线程的锁资源
sync.release(1);
}
public final boolean release(int arg) {
//
if (tryRelease(arg)) {
Node h = head;
// 链表头部位 null 切链表头的节点状态不为 0
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;
// c == 0 则释放当前锁
if (c == 0) {
free = true;
// 将持有锁的线程属性置为 null
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
// unpark 的线程保留在后继节点中,通常只是下一个节点。
// 但如果被取消或明显为空,则从尾部向后遍历以找到实际的未取消后继者。
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 释放锁资源
LockSupport.unpark(s.thread);
}
由于是多线程调用上述的几个方法,这些方法不是线性的进行的,大部分情况下是多个线程调用这些方法穿插进行。