1.CAS(Compare-and-Swap)
CAS在执行更新操作的时候会进行一次比对,如果要修改的值不是他认为的值,那么这次更新操作就不会完成。多个线程进行CAS操作,只有一个线程能成功,失败的线程可以多次尝试。
以AtomicInteger为例
java
public class AtomicInteger extends Number implements java.io.Serializable {
//唯一ID用来验证序列化是否成功
private static final long serialVersionUID = 6214790243416807050L;
// 申请使用unsafe实例
private static final Unsafe unsafe = Unsafe.getUnsafe();
//value的偏移地址
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) {
throw new Error(ex);
}
}
//保证可见性 必须要让其他线程马上看到这个value
private volatile int value;
public AtomicInteger(int initialValue) {
value = initialValue;
}
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev, next;
//不断调用CAS方法来尝试获取并更新值
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return prev;
}
//使用unsafe类调用操作系统底层CAS方法
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}
- ABA问题
线程A想要更新Value值,线程B先改动了Value值然后又更改回来了,这时候线程A使用CAS仍然会成功,ABA问题会导致丢失掉Value变动的这个状态,如果必须要避免这个问题,可以使用乐观锁版本号机制,对每次变动的数据加上一个version,也可以使用传统的互斥同步锁。
2.AQS(AbstractQueuedSynchronizer)
AQS支持独占锁(Exclusive)和共享锁(Share) 两种模式:
- 独占锁:也叫互斥锁、排它锁,只能被一个线程获取到(如
ReentrantLock
、ReadWriteLock
的写锁); - 共享锁:可以被多个线程同时获取(如
CountDownLatch
、ReadWriteLock
的读锁)。
不管是独占锁还是共享锁,本质上都是对AQS内部的一个变量state
的获取,state
是一个原子性的int变量,可用来表示锁状态、资源数等,如下图。
arduino
/**
* The synchronization state.
*/
private volatile int state;
AQS的内部实现了两个队列:同步队列和条件队列。
- 同步队列 :在线程尝试获取资源失败后,会进入同步队列队尾,给前继节点设置一个唤醒信号后,自身进入等待状态(通过
LockSupport.park(this)
),直到被前继节点唤醒。 - 条件队列 :是为
Condition
实现的一个同步器,一个线程可能会有多个条件队列,只有在使用了Condition
才会存在条件队列。需要注意的是,如果一个线程被唤醒(condition.signal()
)后,它会从条件队列转移到同步队列来等待获取锁。
独占
tryAcquire(int)
:独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int)
:独占方式。尝试释放资源,成功则返回true,失败则返回false。
共享
tryAcquireShared(int)
:共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int)
:共享方式。尝试释放资源,成功则返回true,失败则返回false。
isHeldExclusively()
:该线程是否正在独占资源(是否获取到锁)。只有用到Condition才需要实现。
3 基础同步器
acquire(int)
:独占模式下获取锁/资源(写锁lock.lock()
内部实现)
release(int)
:独占模式下释放锁/资源(写锁lock.unlock()
内部实现)
acquireShared(int)
:共享模式下获取锁/资源(读锁lock.lock()
内部实现)
releaseShared(int)
:共享模式下释放锁/资源(读锁lock.unlock()
内部实现)
以下叙述 资源等于锁
3.2 acquire()
3.2.1尝试获取资源
相比于自定以实现同步器少了try
scss
//独占模式获取资源
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
简述流程,首先尝试获取锁,获取失败后就会加入同步队列,并且将这个Node标记为独占模式,如果在等待获取资源过程中产生中断,那么会在获取资源后补充上这一个中断状态。就可以根据这个中断状态,对线程进行下一步的操作。
如果没有获取到资源,节点会执行以下方法
3.2.2获取资源失败
arduino
//获取资源失败后,检查并更新等待状态,如果线程需要阻塞返回true
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
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) {
//如果前节点为CANCELLED状态,那就一直往前找到一个等待状态的节点,并排在它的后边
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 此时前继节点状态为0或PROPAGATE,说明正在等待获取锁/资源,
// 此时需要给前继节点设置一个唤醒信号SIGNAL,但不直接阻塞,
// 因为在阻塞前调用者需要重试来确认它确实不能获取资源。
pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
}
return false;
}
//阻塞当前线程,清除并返回中断状态
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
寻找可安全阻塞的前继节点 如果前节点是SIGNAL大于0也就是CANCLELLED的状态,那么会继续往前找,并且给前节点一个SIGNAL标志。
3.2.3 等待中发生中断
如果在这个过程中发生错误中断,就会调用cancleAcquire方法,将自己的节点退出同步队列,如果前置节点是head,那么就会调用unparkSuccessor方法唤醒指向自己的后置节点。
3.3 release()
java
/**独占模式释放锁/资源*/
public final boolean release(int arg) {
if (tryRelease(arg)) {//尝试释放资源
Node h = head;//头结点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//唤醒head的下一个节点
return true;
}
return false;
}
尝试释放资源,调用tryRelease,直接用state减去arg,如果state == 0 说明资源成功释放(参考可重入锁ReentryLock实现 成功释放资源后会调用unparkSuccessor()唤醒下一个节点
ini
//唤醒给定节点的后继节点
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.3.1 acquireShared(int)
与独占模式几乎一样。区别就是在获取到资源后,会检查是否还有剩余资源 以唤醒后续线程
scss
//获取共享锁
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);//添加一个共享模式Node到队列尾
boolean interrupted = false;
try {
for (;;) {
final Node p = node.predecessor();//获取前节点
if (p == head) {
int r = tryAcquireShared(arg);//前继节点为head,尝试直接获取资源
if (r >= 0) {
//这里比较关键 获取资源成功后会检查是否还有剩余资源
//获取资源成功,设置head为自己,如果有剩余资源继续唤醒之后的线程
setHeadAndPropagate(node, r);
p.next = null; // help GC
return;
}
}
if (shouldParkAfterFailedAcquire(p, node))//检查获取失败后是否可以阻塞
interrupted |= parkAndCheckInterrupt();//阻塞当前线程,清除并返回中断状态
}
} catch (Throwable t) {
cancelAcquire(node);//取消正在等待的节点操作
throw t;
} finally {
if (interrupted)//如果期间线程被中断过,补上中断
selfInterrupt();
}
}
3.3.2 releaseShared(int)
arduino
/**共享模式下释放给定资源数*/
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();//释放资源,并唤醒后继节点
return true;
}
return false;
}
4 条件同步器
本节分析AQS内部对Condition的实现-ConditionObject
4.1 await()
scss
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//添加新节点(当前线程)到条件队列
Node node = addConditionWaiter();
int savedState = fullyRelease(node);//释放锁并返回释放前锁状态
int interruptMode = 0;
while (!isOnSyncQueue(node)) {//当前节点是否在同步队列中
LockSupport.park(this);
//检查等待期间是否被中断过
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//获取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();//清除取消等待的节点
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);//报告中断状态,抛异常或补中断状态
}
如果要使用条件队列 就需要实现AQS的isHeldExclusively()方法用来判断该线程是否持有锁。如果不持有就会抛出IllegalMonitorStateException
4.2 signal()
唤醒条件队列对前面的节点,也就是等待时间最长的节点
java
//移除等待时间最长的节点(firstWaiter)
public final void signal() {
if (!isHeldExclusively())//检查是否持有锁
throw new IllegalMonitorStateException();
Node first = firstWaiter;//获取条件队列的首个节点
if (first != null)
doSignal(first);
}
typescript
//从条件队列唤醒节点线程
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)//先更新firstWaiter
lastWaiter = null; //已经没有等待节点了
first.nextWaiter = null;//解除当前节点的链接
} while (!transferForSignal(first) && //把当前节点转移到等待队列等待获取锁
(first = firstWaiter) != null);
}
也就是会把这个节点转移到同步队列进行等待