1、CAS 原理
CAS 全称是 compare and swap,是一种用于在多线程环境下实现同步功能的机制。
CAS 操作包含三个操作数 – 内存位置、预期数值和新值。
CAS 的实现逻辑是将内存位置处的数值与预期数值进行比较,若相等,则将内存位置处的值替换为新值;若不相等,则重新读取内存值。
其中,ABA问题图解
简单的CAS修改,不防ABA问题:
update table set value = newValue where value = #{oldValue}
带上版本号能防止ABA:
update table set value = newValue ,vision = vision + 1 where value = #{oldValue} and vision = #{vision}
应用:
sun.misc.Unsafe 类中 CAS 的应用
2、AQS 原理
AQS的全称是 AbstractQuenedSynchronizer 抽象队列同步器。该类在java.util.concurrent.locks包。
AQS的核心思想:
如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态;如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH队列
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系。
//等待队列的头,延迟初始化。除了初始化之外,它只能通过方法setHead进行修改。注意:如果head存在,其等待状态保证不会被取消
private transient volatile Node head;
//等待队列的尾部,延迟初始化。仅通过方法enq进行修改,以添加新的等待节点
private transient volatile Node tail;
//同步状态。
private volatile int state;
//返回同步状态的当前值。此操作具有易失性读取的内存语义。 返回: 当前状态值
protected final int getState() {
return state;
}
//设置同步状态的值。此操作具有易失性写入的内存语义。 参数: newState–新的状态值
protected final void setState(int newState) {
state = newState;
}
//如果当前状态值等于预期值,则自动将同步状态设置为给定的更新值。
//参数: expect–期望值 update–新值 返回: 如果成功,则为true。false表示实际值不等于预期值
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
Node节点
static final class Node {
/** 用于指示节点正在共享模式下等待的标记 */
static final Node SHARED = new Node();
/** 用于指示节点正在独占模式下等待的标记 */
static final Node EXCLUSIVE = null;
/** waitStatus值,指示线程已取消 */
static final int CANCELLED = 1;
/** waitStatus值,指示后续线程需要取消连接 */
static final int SIGNAL = -1;
/** waitStatus值,指示线程正在等待状态 */
static final int CONDITION = -2;
/** waitStatus值,指示下一个acquireShared应无条件传播 */
static final int PROPAGATE = -3;
/** 状态字段 */
volatile int waitStatus;
/**
* 链接到当前节点/线程检查等待状态所依赖的前置节点。在排队时分配,只有在退队时才取消(为了GC)。
* 此外,在取消前一个节点时,我们会在查找未取消的前一个节点时短路,因为头节点从未取消,所以该前一 个节点将始终存在:
* 只有成功获取后,节点才会成为头节点。被取消的线程永远不会成功获取,线程只会取消自身,而不会取消任何其他节点
*/
volatile Node prev;
/**
* 链接到当前节点/线程在释放时解析的后续节点。在排队过程中分配,在绕过取消的前导时进行调整,在退出队列时取消(为了GC)。
* enq操作直到连接之后才分配前置节点的下一个字段,所以看到空的下一个字段并不一定意味着节点在队列的末尾。然而,如果下一个字段显示为空,我们可以从尾部扫描上一个字段进行双重检查。
* 取消节点的下一个字段设置为指向节点本身,而不是null,以使isOnSyncQueue的工作更轻松
*/
volatile Node next;
/**
* 使此节点排队的线程。构造时初始化,使用后清空
*/
volatile Thread thread;
/**
* 链接到下一个等待条件的节点,或共享的特殊值。因为条件队列只有在独占模式下保持时才被访问,所以我们只需要一个简单的链接队列来在节点等待条件时保持节点。
* 然后,它们被转移到队列中重新获取。由于条件只能是独占的,我们通过使用特殊值来指示共享模式来保存字段。
*/
Node nextWaiter;
/**
* 如果节点正在共享模式下等待,则返回true
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* 返回上一个节点,如果为null,则抛出NullPointerException。
* 当前置项不能为空时使用。空检查可以省略,但它的出现是为了帮助VM。 返回: 此节点的前置节点
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // 用于建立初始头部或共享标记
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
主要应用
ReentrantLock(可重入锁)
ReentrantReadWriteLock(可重入读写锁)
Semaphore(信号)
CountDownLatch(倒计时闩锁)