什么是AQS
AQS,AbstractQueuedSynchronizer,抽象队列同步器。它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。
AQS的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态。
ReentrantLock
加锁逻辑
默认构造方法,创建一个NonfairSync,此类继承于AbstractQueuedSynchronizer。
public ReentrantLock() {
sync = new NonfairSync();
}
final void lock() {
//判断是否已经有人加锁
if (compareAndSetState(0, 1))
//设置当前线程是加了独占锁的线程,标识出来自己是加锁的线程
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
AQS里有一个核心的变量state,代表了锁的状态。如果state=0,代表没人加过锁,此时可以加锁,把这个state设置为1。
可重入锁逻辑:
假设线程1再次lock(),此时state=1,所以走acquire(1)这个方法。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程->线程1
final Thread current = Thread.currentThread();
int c = getState();
//再次判断是否有人加过锁,防止锁已经被释放
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果加锁线程一致,此时他们都是线程1
else if (current == getExclusiveOwnerThread()) {
//nextc=1+1=2
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//state=2
setState(nextc);
return true;
}
return false;
}
线程2进行加锁:
public final void acquire(int arg) {
//因为线程1占用着锁,因为是不同的线程,执行tryAcquire(arg)返回false。
if (!tryAcquire(arg) &&
//addWaiter(Node.EXCLUSIVE), arg)->线程2代表的Node
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
private Node addWaiter(Node mode) {
//将当前线程(线程2)封装成了一个Node。处于阻塞等待状态的线程可以封装为一个Node双向链表(队列)。
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;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
//判断head是否为null,如果是null的话,就将head设置为空Node节点。
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
//比较tail变量是否为t,如果为t的话,那么tail指针就指向node。
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取到node的上一个节点,即prev指针指向的节点
final Node p = node.predecessor();
//线程2尝试再次加锁,如果加锁成功,会将线程2对应的Node从队列中移除。
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);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//默认情况下,watiStatus=0。
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 将空Node的waitStatus设置为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
//将一个线程进行挂起,必须有另外一个线程来对当前线程执行unpark操作,才能唤醒挂起的线程。
LockSupport.park(this);
return Thread.interrupted();
}
公平锁:
ReentrantLock默认是非公平锁,当传入参数为true时,为公平锁。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
逻辑与非公平锁基本上一致,唯一的不同在于调用tryAcquire()时增加了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;
}
}
return false;
}
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
公平锁,任何一个线程过来会先判断一下,当前是否有人在排队,而且是不是自己在排队,如果不是的话,说明有别人在排队,此时自己不能尝试加锁,直接入队阻塞等待,先来后到的顺序,后来的人一定是排到队列里去等待。
lock()流程图:
释放锁逻辑
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;
//设置state=0
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
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;
}
//后继节点不为null时唤醒该线程
if (s != null)
LockSupport.unpark(s.thread);
}
unlock()流程图:
锁优化策略
1、如果一些线程只会写一个变量,另外一个线程是来读取这个变量的值,那么此时优先使用volatile。比如通过修改volatile标志位实现服务优雅停机机制。
2、如果线程都要写,并且只是简单的数值累加或者变更的操作,此时可以用 Atomic原子类。比如微服务注册中心项目中的服务心跳计数器。
3、多线程并发访问一块共享数据, 读多写少的场景,建议使用读写锁。比如微服务注册中心项目中每隔30秒增量拉取注册表。
4、尽量保证加锁的时间是很短的,不要在加锁之后,执行一些磁盘文件读写、网络IO读写,导致锁占用的时间过于长。
5、分段加锁,实践,尽可能的减少一个线程占用锁的时间。
6、尽可能对不同功能分离锁的使用,在使用不同的功能的时候,可以用不同的锁,这样降低线程竞争锁的冲突。比如阻塞队列就使用了两把锁,队头是一把锁,队尾是一把锁,你从队列尾巴插进去是加的一把锁,从队头消费数据使用的是另外一把锁,入队和出队的操作,就不会因为锁产生冲突。
7、避免在循环中频繁的加锁以及释放锁。
8、尽量减少高并发场景中线程对锁的争用,比如用多级缓存机制。