一、JAVA并发概述
JAVA开发中常见的并发问题有线程同步、线程协作、线程分工等。JUI包(java.util.concurrent)中提供了多种API和工具类来解决各种并发问题。这几类并发问题往往不是独立存在的,开发者可根据实际问题决定采用哪种方式。本文主要介绍线程同步中的Lock&AQS原理。
线程同步是指:当有一个线程在对临界资源进行操作时,其他线程都不可以操作该临界资源,直到该线程完成操作,其他线程才能继续操作。实现线程同步的方法有:加锁和无锁。
加锁 | 无锁 | |
---|---|---|
含义 | 当一个线程访问临界资源时,会对临界资源加锁,当其他线程访问时会被阻塞。 (实际实现中考虑到效率等原因,一般不会立即阻塞,而是先进行自旋等操作) | 当一个线程访问临界资源时,如果该资源正在被其他线程访问,则该线程会访问失败并返回false或者自旋直至成功。 |
实现方式 | synchronized、Lock | CAS、Atomic原子类等 |
如上所示,JAVA开发中常用的加锁方式有两种:synchronized关键字、Lock。
// Lock接口中定义了加锁/释放锁的方法;
// Lock的实现类有ReentrantLock、ReentrantReadWriteLock.ReadLock、ReentrantReadWriteLock.WriteLock等
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
下面继续介绍synchronized、Lock加锁的用法。
二、ReentrantLock
2.1 基本用法
ReentrantLock实现了Lock接口。ReentrantLock即可重入锁,指一个线程(已经获得锁)可以对临界资源重复加锁。对比synchronized的使用,ReentrantLock使用方式如下:
// 1.synchronized使用方式
public synchronized void testLock1() {
doSomething();
}
// 2.ReentrantLock使用方式
public void testLock2() {
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
doSomething();
} finally {
lock.unlock();
}
}
可以看到,相比synchronized方式,ReentrantLock在加锁和释放锁时都需要在代码中显式调用,而synchronized方式会在方法执行前自动加锁,方法执行结束后自动释放锁。ReentrantLock的优点在于ReentrantLock提供了多种获取锁的方式(尝试获取锁、超时获取锁、可中断锁等)、支持公平锁/非公平锁、可结合Condition关联多个条件队列。
ReentrantLock | synchronized | |
---|---|---|
使用方式 | 需要手动加锁/释放锁 | 使用结束后自动释放锁 |
锁类型 | 支持公平锁/非公平锁 | 非公平锁 |
获取锁方式 | 提供了多种获取锁的方式 1.尝试获取锁:获取锁失败时会立即返回false; 2.超时获取锁:获取锁时可设置超时时间 3.响应中断:线程可以在被阻塞时响应中断 | 获取锁失败时会阻塞当前线程,直到获取锁成功 |
条件队列 | 可创建多个Condition实现关联多个条件队列。 通过Condition.await()、Condition.signal()实现。 | 只有一个条件队列。 通过Object.wait()、Object.notify()实现。 |
锁实现机制 | 依赖AQS | 依赖Monitor机制(每一个Java对象都有一把看不见的锁); 锁升级机制; Monitor依赖于底层操作系统的Mutex Lock(互斥锁)来实现线程同步; |
2.2 ReentrantLock源码分析
下面从ReentrantLock的构造函数,以及ReentrantLock.lock()方法来分析加锁过程。如下所示,创建ReentrantLock对象时支持公平锁/非公平锁(默认采用非公平锁),ReentrantLock.lock()的实现依赖其内部类ReentrantLock.Sync:
public class ReentrantLock implements Lock {
private final Sync sync;
public ReentrantLock() {
// 默认是非公平锁
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
// 支持公平锁/非公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
public void lock() { sync.lock(); }
}
2.3 非公平锁&公平锁
内部类ReentrantLock.Sync有两个子类:NonfairSync、FairSync,对应着非公平锁、公平锁。二者的区别在于:获取锁的顺序是否公平,是否与请求获取锁的顺序一样。分两种情况讨论:
- 情况1:当新的节点请求获取锁时,锁正在被持有,非公平锁与公平锁都会把新节点放到等待队列;
- 情况2:当新的节点请求获取锁时,锁刚好被释放,非公平锁会先尝试获取锁,获取失败才会进入等待队列。
从源码上看,非公平锁NonfairSync和公平锁FairSync的代码区别主要在于:
- 在加锁方法lock()中,非公平锁NonfairSync会先尝试CAS更新state状态值,更新成功则直接获得锁;
- 在尝试获取锁方法tryAcquire()中,非公平锁NonfairSync会再次先尝试CAS更新state状态值,更新成功则直接获得锁;而公平锁FairSync会多一个hasQueuedPredecessors()判断,判断是否有其他线程等候了更长时间。
public class ReentrantLock implements Lock {
/**
* 非公平锁
*/
static final class NonfairSync extends Sync {
/**
* 获取锁
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
/**
* 尝试获取锁;
* 源码中nonfairTryAcquire()在其父类中ReentrantLock.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()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
/**
* 公平锁
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* 尝试获取锁
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 相比非公平锁,这里多了一个判断:hasQueuedPredecessors()
// hasQueuedPredecessors():判断等待获取锁的队列中,是否有其它等待时间更长的线程
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;
}
}
}
2.4 ReentrantLock.Sync
上面提到的非公平锁NonfairSync、公平锁FairSync,都是ReentrantLock.Sync的子类。
public class ReentrantLock implements Lock {
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
/**
* 尝试释放锁
*/
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;
}
}
}
可以看到,ReentrantLock.Sync、ReentrantLock.NonfairSync、ReentrantLock.FairSync只是实现了加锁/释放锁的主体逻辑,而具体的工作如修改同步状态、等待队列、线程阻塞都是在其父类AbstractQueuedSynchronizer中实现的。即AbstractQueuedSynchronizer提供了一个同步框架,其子类在这套同步框架的基础上实现锁功能。
2.5 总结
综合上面的分析,AQS提供了一个基础的同步框架,ReentrantLock在AQS的基础上实现了加锁/解锁的功能。ReentrantLock、ReentrantLock.NonfairSync、ReentrantLock.FairSync、AQS的关系如下:
三、AQS源码分析
3.1 AQS基本结构
AQS即AbstractQueuedSynchronizer,AQS提供了一个基于FIFO等待队列的同步框架。AQS的子类在这套框架基础上实现AQS的几个protect方法来实现同步锁,例如ReentrantLock.Sync、ReentrantReadWriteLock.Sync、CountDownLatch.Sync、Semaphore.Sync等。
AQS使用volatile int类型的state表示同步状态,用CAS修改state值,用FIFO队列管理获取临界资源的线程。如下所示AQS的FIFO队列持有head节点和tail节点:
public abstract class AbstractQueuedSynchronizer {
/** 队列头节点 */
private transient volatile Node head;
/** 队列尾节点 */
private transient volatile Node tail;
/** 同步状态 */
private volatile int state;
private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
/**
* CAS设置state;用于尝试获取锁/尝试释放锁操作
*/
protected final boolean compareAndSetState(int expect, int update) {
return U.compareAndSwapInt(this, STATE, expect, update);
}
}
3.2 Node定义
其中,Node是把单个线程封装成的AQS内部类,每个Node持有前后节点的引用:
public abstract class AbstractQueuedSynchronizer {
static final class Node {
/**
* 当前节点的状态,包括SIGNAL 、CANCELLED 、CONDITION、PROPAGATE、0
*/
volatile int waitStatus;
/** 前一个节点 */
volatile Node prev;
/** 下一个节点 */
volatile Node next;
/** 指向的线程 */
volatile Thread thread;
/** Condition下的下一个节点;或SHARED节点 */
Node nextWaiter;
private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
/**
* CAS设置当前节点状态
*/
final boolean compareAndSetWaitStatus(int expect, int update) {
return U.compareAndSwapInt(this, WAITSTATUS, expect, update);
}
/**
* CAS设置下一个节点
*/
final boolean compareAndSetNext(Node expect, Node update) {
return U.compareAndSwapObject(this, NEXT, expect, update);
}
}
}
3.3 lock获取锁过程
以为非公平锁为例分析ReentrantLock.lock()获取锁流程源码:
public class ReentrantLock implements Lock {
/**
* 非公平锁
*/
static final class NonfairSync extends Sync {
/**
* 获取锁
*/
final void lock() {
if (compareAndSetState(0, 1))
// 1.尝试CAS设置state值,设置成功,则获取锁
setExclusiveOwnerThread(Thread.currentThread());
else
// 2.CAS设置失败,则执行acquire()进入获取锁流程
acquire(1);
}
/**
* 尝试获取锁;
*/
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
/**
* 非公平方式尝试获取锁;
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 同上,这个时候有可能其他线程已经释放了锁,所以再次尝试CAS获取锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
// 当前线程已经是获取锁的线程,则更新state的值,获取锁成功,即可重入锁
int nextc = c + acquires;
setState(nextc);
return true;
}
return false;
}
}
}
/**
* 基于FIFO等待队列的同步框架
*/
public abstract class AbstractQueuedSynchronizer {
/**
* 获取锁
*/
public final void acquire(int arg) {
// 1.tryAcquire()尝试获取锁;获取成功则返回;
// 2.如果获取失败,则执行addWaiter()创建Node节点(引用着当前线程)并加入同步队列
// 3.加入同步队列后,会执行acquireQueued()再次尝试获取锁(因为这时其他线程可能已经释放锁)
// 4.仍然获取失败,则调用LockSupport.park(this)阻塞当前线程;
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 如果阻塞的过程被中断(超时或者其他原因),则会执行interrupt()
selfInterrupt();
}
/**
* 把Node节点添加到同步队列
*/
private Node addWaiter(Node mode) {
Node node = new Node(mode);
for (;;) {
Node oldTail = tail;
if (oldTail != null) {
// 1.用Unsafe把当前节点的前一个节点设置为同步队列的尾节点
U.putObject(node, Node.PREV, oldTail);
// 2.用Unsafe更新同步队列的尾节点为当前节点
if (compareAndSetTail(oldTail, node)) {
// 3.旧尾节点的next节点更新为当前节点
oldTail.next = node;
return node;
}
} else {
// 初始化同步队列,即用Unsafe设置队列HEAD值;
// HEAD节点是空节点,创建完HEAD节点后继续for循环把当前节点添加到队列
initializeSyncQueue();
}
}
}
/**
* 已经在队列中的节点获取锁
*/
final boolean acquireQueued(final Node node, int arg) {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 如果当前节点的前节点即HEAD节点(HEAD是空节点),说明该节点是同步队列第一个节点,则再次尝试获取锁
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null;
return interrupted;
}
// 获取失败后判断是否阻塞线程,如果是则继续执行parkAndCheckInterrupt()
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
}
/**
* 阻塞当前线程
*/
private final boolean parkAndCheckInterrupt() {
// 阻塞线程
LockSupport.park(this);
// 当线程恢复执行后,返回是否中断线程
return Thread.interrupted();
}
}
3.4 unlock释放锁过程
下面分析ReentrantLock.unlock()释放锁的流程源码:
public class ReentrantLock implements Lock {
public void unlock() {
sync.release(1);
}
abstract static class Sync extends AbstractQueuedSynchronizer {
/**
* 释放锁
*/
protected final boolean tryRelease(int releases) {
// 1.计算释放锁后的state值(state值即同步状态)
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
// 2.如果值为0,则设置持有锁的线程为null
setExclusiveOwnerThread(null);
}
// 3.更新state值
setState(c);
return free;
}
}
}
/**
* 基于FIFO等待队列的同步框架
*/
public abstract class AbstractQueuedSynchronizer {
/**
* 释放锁
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 释放锁后,唤醒等待线程
unparkSuccessor(h);
return true;
}
return false;
}
/**
* 释放锁后,唤醒等待线程
*/
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
node.compareAndSetWaitStatus(ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node p = tail; p != node && p != null; p = p.prev)
if (p.waitStatus <= 0)
s = p;
}
if (s != null)
// 唤醒线程
LockSupport.unpark(s.thread);
}
}
3.5 总结
AQS提供了基于FIFO等待队列的同步框架,包括同步状态state、同步队列、线程阻塞、互斥锁/共享锁等基础功能。ReentrantLock在AQS同步框架的基础上,实现了获取锁/释放锁的具体逻辑(例如非公平锁/公平锁的获取锁流程等);ReentrantReadWriteLock在AQS同步框架的互斥锁/共享锁基础上,实现了读写锁的功能(写锁WriteLock是互斥锁,而读锁ReadLock是共享锁)。开发者也可以在AQS的基础上自定义其他锁实现。
The End
欢迎关注我,一起解锁更多技能:BC的掘金主页~💐 BC的CSDN主页~💐💐
JVM结构官方文档:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.3
英文参考文档:http://tutorials.jenkov.com/java-concurrency/index.html
concurrent工具包参考文档:http://tutorials.jenkov.com/java-util-concurrent/index.html
锁类型:https://tech.meituan.com/2018/11/15/java-lock.html
AQS:https://tech.meituan.com/2019/12/05/aqs-theory-and-apply.html
JAVA并发参考文档:http://concurrent.redspider.group/article/01/1.html
线程池参考文档:https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html