Java多线程与并发编程–ReentrantLock
1.1 底层原理
public class ReentrantLock implements Lock, Serializable
ReentrantLock底层使用了 CAS + AQS队列
1.1.1 CAS
- 比较并交换,是一个原子操作【CPU指令】。当且仅当 当前值与预期值相等时,更新为新值。
- 是乐观锁的实现机制
- 在Java中,通过调用本地方法的方式实现CAS
- CAS是整个JUC包的底层支持
1.1.2 CAS的缺点
- ABA问题【解决方案:使用版本号】
- 循环判断时间长的话,开销会变大
- 只能保证一个共享变量的原子操作
1.1.3 AQS
- AbstractQueuedSynchronizer
- 构建锁和同步器的框架
- ReentrantLock, Semaphore, ReentrantReadWriteLock, SynchronousQueue, Future等都是基于AQS构建的同步器
1.1.4 AQS原理
若被请求的共享资源空闲,则将当前线程设置为工作线程,并将共享资源设置为锁定状态。
若请求的共享资源被占用,则将当前线程加入到CLH队列并等待
AQS使用一个int成员变量来表示同步状态,对该状态的修改使用CAS
private volatile int state;//共享变量,使用volatile修饰保证线程可见性
1.1.5 AQS对资源的共享方式
- 独占方式:只有一个线程能执行,如
ReentrantLock
- 共享方式:多个线程可以同时执行,如
Semaphore/CountDownLatch
1.1.6 AQS底层使用模板方法模式
- 同步器的设计基于模板方法模式,自定义同步器的一般步骤:
- 使用者继承
AbstractQueuedSynchronizer
并重写指定的方法。(这些重写方法很简单,无非是对于共享资源 state 的获取和释放)- 将 AQS 组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。
1.2 ReentrantLock的执行流程
- 线程先通过CAS尝试获取锁
- 若当前锁已经被占用,则该线程加入AQS队列进行等待
- 当前锁被释放,在AQS队列等待的线程的争抢策略如下:
- 公平锁:按照队列的顺序,先到先得
- 非公平锁:在队列中的线程要获取锁,先通过两次CAS操作抢锁,若抢不到,则线程再次进入队列等待唤醒
ReentrantLock的两个构造函数
public ReentrantLock() { sync = new NonfairSync(); //默认,非公平 } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); //根据参数创建 }
1.3 lock() 和 unlock()的实现
1.3.1 lock()函数
如果成功通过CAS修改了state,指定当前线程为该锁的独占线程,标志自己成功获取锁。
如果CAS失败的话,调用acquire();
final void lock() { //非公平锁 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } final void lock() { //公平锁 acquire(1); }
acquire()函数:
首先,调用tryAcquire(),会尝试再次通过CAS修改state为1,
如果失败而且发现锁是被当前线程占用的,就执行重入(state++);
如果锁是被其他线程占有,那么当前线程执行tryAcquire返回失败,并且执行addWaiter()进入等待队列,并挂起自己interrupt()。
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
tryAcquire() 函数
检查state字段,
若为0,表示锁未被占用,那么尝试占用;
若不为0,检查当前锁是否被自己占用,若被自己占用,则更新state字段,表示重入锁的次数。
如果以上两点都没有成功,则获取锁失败,返回false。
protected final boolean tryAcquire(int acquires) { //注意:这是公平的tryAcquire() final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //需要先判断自己是不是队头的节点 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } } final boolean tryAcquire(int acquires) { //注意:这是非公平的tryAcquire() //获取当前线程 final Thread current = Thread.currentThread(); //获取state变量值 int c = getState(); if (c == 0) { //没有线程占用锁 //没有 !hasQueuedPredecessors() 判断,不考虑自己是不是在队头,直接申请锁 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"); // 更新state值为新的重入次数 setState(nextc); return true; } //获取锁失败 return false; }
addWaiter() 函数
当前线程加入AQS双向链表队列。
写入之前需要将当前线程包装为一个 Node 对象(addWaiter(Node.EXCLUSIVE))。
首先判断队列是否为空,不为空时则将封装好的 Node 利用 CAS 写入队尾,如果出现并发写入失败就需要调用 enq(node); 来写入了。
/** * 将新节点和当前线程关联并且入队列 * @param mode 独占/共享 * @return 新节点 */ private Node addWaiter(Node mode) { //初始化节点,设置关联线程和模式(独占 or 共享) Node node = new Node(Thread.currentThread(), mode); // 获取尾节点引用 Node pred = tail; // 尾节点不为空,说明队列已经初始化过 if (pred != null) { node.prev = pred; // 设置新节点为尾节点 if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } // 尾节点为空,说明队列还未初始化,需要初始化head节点并入队新节点 enq(node); return node; } private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
enq()的处理逻辑就相当于
自旋
加上CAS
保证一定能写入队列。acquireQueued() 函数
写入队列后,需要挂起当前线程
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
首先会根据 node.predecessor() 获取到上一个节点是否为头节点,如果是则尝试获取一次锁,获取成功就万事大吉了。
如果不是头节点,或者获取锁失败,则会根据上一个节点的 waitStatus 状态来处理(shouldParkAfterFailedAcquire(p, node))。
waitStatus 用于记录当前节点的状态,如节点取消、节点等待等。
shouldParkAfterFailedAcquire(p, node) 返回当前线程是否需要挂起,如果需要则调用 parkAndCheckInterrupt():
private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }
他是利用
LockSupport
的part
方法来挂起当前线程的,直到被唤醒。
1.3.2 unlock() 函数
释放时候,state–,通过state==0判断锁是否完全被释放。
成功释放锁的话,唤起一个被挂起的线程
public void unlock() { sync.release(1); } 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; }
1.4 JUC包底层支撑结构
1.5 ReentrantLock的基本使用
1.5.1 简单使用
public class Test1 {
private static final Lock lock = new ReentrantLock();
public static void main(String[] args) {
new Thread(() -> test(), "线程A").start();
new Thread(() -> test(), "线程B").start();
}
public static void test(){
lock.lock();
try{
System.out.println(Thread.currentThread().getName() + "获得锁");
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e){
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放锁");
lock.unlock();
}
}
}
线程A获得锁
线程A释放锁
线程B获得锁
线程B释放锁
1.5.2 公平锁实现
public class Test2 {
private static final Lock lock = new ReentrantLock(true);
public static void main(String[] args) {
new Thread(() -> test(), "线程A").start();
new Thread(() -> test(), "线程B").start();
new Thread(() -> test(), "线程C").start();
new Thread(() -> test(), "线程D").start();
new Thread(() -> test(), "线程E").start();
}
public static void test(){
for (int i = 0; i < 2; i++) {
lock.lock();
try{
System.out.println(Thread.currentThread().getName() + "获得锁");
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e){
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放锁");
lock.unlock();
}
}
}
}
线程A获得锁
线程A释放锁
线程B获得锁
线程B释放锁
线程C获得锁
线程C释放锁
线程D获得锁
线程D释放锁
线程E获得锁
线程E释放锁
线程A获得锁
线程A释放锁
线程B获得锁
线程B释放锁
线程C获得锁
线程C释放锁
线程D获得锁
线程D释放锁
线程E获得锁
线程E释放锁
1.5.3 非公平锁实现
public class Test2 {
private static final Lock lock = new ReentrantLock();
public static void main(String[] args) {
new Thread(() -> test(), "线程A").start();
new Thread(() -> test(), "线程B").start();
new Thread(() -> test(), "线程C").start();
new Thread(() -> test(), "线程D").start();
new Thread(() -> test(), "线程E").start();
}
public static void test(){
for (int i = 0; i < 2; i++) {
lock.lock();
try{
System.out.println(Thread.currentThread().getName() + "获得锁");
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e){
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放锁");
lock.unlock();
}
}
}
}
线程A获得锁
线程A释放锁
线程A获得锁
线程A释放锁
线程B获得锁
线程B释放锁
线程B获得锁
线程B释放锁
线程C获得锁
线程C释放锁
线程C获得锁
线程C释放锁
线程D获得锁
线程D释放锁
线程D获得锁
线程D释放锁
线程E获得锁
线程E释放锁
线程E获得锁
线程E释放锁
1.5.4 响应中断
public class Test3 {
private static final Lock lock1 = new ReentrantLock();
private static final Lock lock2 = new ReentrantLock();
public static void main(String[] args) {
Thread a = new Thread(() -> test(), "线程A");
Thread b = new Thread(() -> test(), "线程B");
a.start();
b.start();
a.interrupt();
}
public static void test() {
try {
lock1.lockInterruptibly();
TimeUnit.SECONDS.sleep(50);
lock2.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock1.unlock();
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " 正常结束");
}
}
}
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1220)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at thread.reentrantlock.Test3.test(Test3.java:21)
at thread.reentrantlock.Test3.lambda$main$0(Test3.java:12)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "线程A" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
at thread.reentrantlock.Test3.test(Test3.java:27)
at thread.reentrantlock.Test3.lambda$main$0(Test3.java:12)
at java.lang.Thread.run(Thread.java:748)
线程B 正常结束