目录
使用AtomicStampedReference解决ABA问题
使用AtomicMarkableReference解决ABA问题
Thread.sleep()和Object.wait()的区别
JUC中多数类是通过volatile和CAS来实现的,CAS本质上提供的是一种无锁方案,而Synchronized和Lock是互斥锁方案。Java原子类本质上是使用CAS,而CAS底层是使用Unsafe类实现的,因此,想要学好JUC,必须要对CAS和底层的Unsafe原理彻底了解清楚。本篇文章通过流程图以及相关脑图等手段,力求能分析透彻。好吧,我们一起来学习吧。
CAS
什么是CAS
CAS的全称为Compare-And-Swap,直译就是对比交换。
CAS操作是原子性的,所以多线程并发使用CAS更新数据时,可以不使用锁。
JDK中大量使用了CAS来更新数据而防止加锁(synchronized 重量级锁)来保持原子更新。
不过呢,想要了解CAS底层原理,还得先了解一个Unsafe类,Java原子类是通过UnSafe类实现的。
Unsafe类
Unsafe类介绍
Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全的底层操作,如直接访问系统内存资源、自主管理内存资源等。
Unsafe大量的方法都是native方法,基于C++语言实现,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。
Unsafe类中的CAS
操作系统层面的CAS是一条CPU的原子指令(cmpxchg指令),正是由于该指令具备原子性,因此使用CAS操作数据时不会造成数据不一致的问题,Unsafe提供的CAS方法直接通过native方式(封装C++代码)调用了底层的CPU指令cmpxchg。
查看源码:
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)
{
int i;
do
i = getIntVolatile(paramObject, paramLong);
while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));
return i;
}
public final long getAndAddLong(Object paramObject, long paramLong1, long paramLong2)
{
long l;
do
l = getLongVolatile(paramObject, paramLong1);
while (!compareAndSwapLong(paramObject, paramLong1, l, l + paramLong2));
return l;
}
public final int getAndSetInt(Object paramObject, long paramLong, int paramInt)
{
int i;
do
i = getIntVolatile(paramObject, paramLong);
while (!compareAndSwapInt(paramObject, paramLong, i, paramInt));
return i;
}
public final long getAndSetLong(Object paramObject, long paramLong1, long paramLong2)
{
long l;
do
l = getLongVolatile(paramObject, paramLong1);
while (!compareAndSwapLong(paramObject, paramLong1, l, paramLong2));
return l;
}
public final Object getAndSetObject(Object paramObject1, long paramLong, Object paramObject2)
{
Object localObject;
do
localObject = getObjectVolatile(paramObject1, paramLong);
while (!compareAndSwapObject(paramObject1, paramLong, localObject, paramObject2));
return localObject;
}
从源码中发现,内部使用自旋的方式(循环的方式)进行CAS更新(while循环进行CAS更新,如果更新失败,则循环再次重试)。
需要注意的是:
原子操作其实只支持这三个方法,compareAndSwapObject、compareAndSwapInt和compareAndSwapLong,都是native方法;
public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3);
public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);
public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);
查看底层源码,位于unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
可以看到它通过 Atomic::cmpxchg 来实现比较和替换操作。
如果是多处理器,为cmpxchg指令添加lock前缀。反之,就省略lock前缀(单处理器会不需要lock前缀提供的内存屏障效果)。这里的lock前缀就是使用了处理器的总线锁(最新的处理器都使用缓存锁代替总线锁来提高性能)。
UnSafe类详解
如上图所示,Unsafe提供的API大致可分为内存操作、CAS、Class相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等几类。
JUC原子类
在多线程并发执行时,诸如“++”或“--”类的运算不具备原子性,不是线程安全的操作。通常情况下,大家会使用synchronized或锁将这些线程不安全的操作变成同步操作,但是这样会降低并发程序的性能。
与synchronized同步机制相比,JDK原子类是基于CAS轻量级原子操作的实现,使得程序运行效率变得更高。
JUC中的Atomic原子操作包
所谓Atomic类,指的是具有原子操作特征的类。
JUC并发包中的原子类都存放在java.util.concurrent.atomic类路径下。
类型有以下:
基础原子类
以AtomicInteger为例子
public final int get() //获取当前的值
public final int getAndSet(int newValue) //获取当前的值,然后设置新的值
public final int getAndIncrement() //获取当前的值,然后自增
public final int getAndDecrement() //获取当前的值,然后自减
public final int getAndAdd() //获取当前的值,并加上预期的值
public final boolean compareAndSet(int expect, int update) //通过CAS方式设置整数值
基础原子类主要通过CAS自旋+volatile的方案实现,既保障了变量操作的线程安全性,又避免了synchronized重量级锁的高开销,使得Java程序的执行效率大为提升。
数组原子类
以AtomicIntegerArray为例子
public final int get(int i) //获取index为i位置元素的值
public final int getAndSet(int newValue) //获取当前的值,然后设置新的值
public final int getAndIncrement(int i) //获取index为i位置的值,然后自增
public final int getAndDecrement(int i) //获取index为i位置的值,然后自减
public final int getAndAdd(int i) //获取index为i位置的值,并加上预期的值
//如果输入的数组等于预期值,就以原子方式将位置为expect的元素设置为输入值
public final boolean compareAndSet(int expect, int update)
//lazySet方法可能导致其他线程在之后的一小段时间内还是可以读到旧的值
public final void lazySet(int i, int newValue)
引用原子类
基础的原子类型只能保证一个变量的原子操作,当需要对多个变量进行操作时,CAS无法保证原子性操作,这时可以用AtomicReference(原子引用类型)保证对象引用的原子性。
简单来说,如果需要同时保障对多个变量操作的原子性,就可以把多个变量放在一个对象中进行操作。
注意点:
使用原子引用类型AtomicReference包装了User对象之后,只能保障User引用的原子操作,对被包装的User对象的字段值修改时不能保证原子性。
属性更新原子类
如果需要保障对象某个字段(或者属性)更新操作的原子性,就需要用到属性更新原子类。
步骤:
- 更新的对象属性必须使用public volatile修饰符
- 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须调用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性
示例
@Data
@AllArgsConstructor
public class User {
public volatile int age;
private String name;
}
public class AtomicIntegerFieldUpdaterTest {
public static void main(String[] args) {
AtomicIntegerFieldUpdater<User> updater =
AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
User user = new User(1,"DonaldCen");
System.out.println(JSONObject.toJSONString(user));
System.out.println(JSONObject.toJSONString(updater.getAndIncrement(user)));
System.out.println(JSONObject.toJSONString(updater.getAndAdd(user,100)));
System.out.println(JSONObject.toJSONString(updater.get(user)));
}
}
结果:
{"age":1,"name":"DonaldCen"}
1
2
102
注意:age属性必须是 public volatile,否则会报错。
ABA问题
由于CAS原子操作性能高,因此其在JUC包中被广泛应用,只不过如果使用得不合理,CAS原子操作就会存在ABA问题。
什么是ABA问题?
举一个例子来说明。比如一个线程A从内存位置M中取出V1,另一个线程B也取出V1。现在假设线程B进行了一些操作之后将M位置的数据V1变成了V2,然后又在一些操作之后将V2变成了V1。之后,线程A进行CAS操作,但是线程A发现M位置的数据仍然是V1,然后线程A操作成功。
解决方案
解决ABA问题很多都是采取乐观锁的实现版本都是使用版本号(Version)方式。乐观锁每次在执行数据的修改操作时都会带上一个版本号,版本号和数据的版本号一致就可以执行修改操作并对版本号执行加1操作,否则执行失败。因为每次操作的版本号都会随之增加,所以不会出现ABA问题,因为版本号只会增加,不会减少。
JUC包也提供两个类解决ABA问题。
使用AtomicStampedReference解决ABA问题
参考乐观锁的版本号,JDK提供了一个AtomicStampedReference类来解决ABA问题。AtomicStampReference在CAS的基础上增加了一个Stamp(印戳或标记),使用这个印戳可以用来觉察数据是否发生变化,给数据带上了一种实效性的检验。
来看一下AtomicStampedReference源码
构造器
//V表示要引用的原始数据,initialStamp表示最初的版本印戳(版本号)
public AtomicStampedReference(V initialRef, int initialStamp)
常用方法
//获取被封装的数据
public V getReference();
//获取被封装的数据的版本印戳
public int getStamp();
AtomicStampedReference的CAS操作的定义如下:
//expectedReference 预期引用值,newReference 更新后的引用值,expectedStamp 预期印戳,newStamp更新后的印戳
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp);
使用AtomicMarkableReference解决ABA问题
AtomicMarkableReference是AtomicStampedReference的简化版,不关心修改过几次,只关心是否修改过。
因此,其标记属性mark是boolean类型,而不是数字类型,标记属性mark仅记录值是否修改过。
AtomicMarkableReference适用于只要知道对象是否被修改过,而不适用于对象被反复修改的场景。
CAS性能
在争用激烈的场景下,会导致大量的CAS空自旋,大量的CAS空自旋会浪费大量的CPU资源,大大降低了程序的性能。
在高并发场景下如何提升CAS操作的性能呢?可以使用LongAdder替代AtomicInteger。
以空间换时间:LongAdder
原理
AtomicLong:
- AtomicLong使用内部变量value保存着实际的long值,所有的操作都是针对该value变量进行的
- 也就是说,在高并发环境下,value变量其实是一个热点,也就是N个线程竞争一个热点
- 重试线程越多,就意味着CAS的失败概率更高,从而进入恶性CAS空自旋状态
LongAdder:
- LongAdder的基本思路是分散热点,将value值分散到一个数组中,不同线程会命中到数组的不同槽(元素)中,各个线程只对自己槽中的那个值进行CAS操作
- 这样热点就被分散了,冲突的概率就小很多
- 使用LongAdder,即使线程数再多也不必担心,各个线程会分配到多个元素上去更新,增加元素个数,就可以降低value的“热度”,AtomicLong中的恶性CAS空自旋就解决了。
- 如果要获得完整的LongAdder存储的值,只要将各个槽中的变量值累加,返回最终累加之后的值即可。
内部结构
LongAdder的内部成员包含一个base值和一个cells数组。在最初无竞争时,只操作base的值;当线程执行CAS失败后,才初始化cells数组,并为线程分配所对应的元素。
Striped64的设计核心思路是通过内部的分散计算来避免竞争,以空间换时间。
LongAdder的base类似于AtomicInteger里面的value,在没有竞争的情况下,cells数组为null,这时只使用base进行累加;
而一旦发生竞争,cells数组就上场了。
cells数组第一次初始化长度为2,以后每次扩容都变为原来的两倍,一直到cells数组的长度大于等于当前服务器CPU的核数。为什么呢?同一时刻能持有CPU时间片去并发操作同一个内存地址的最大线程数最多也就是CPU的核数。
在存在线程争用的时候,每个线程被映射到cells[threadLocalRandomProbe &cells.length]位置的Cell元素,该线程对value所做的累加操作就执行在对应的Cell元素的值上,最终相当于将线程绑定到cells中的某个Cell对象上。
接下来,要学的是重中之重的了,虽然很重要,但个人认为学习起来比较吃力,体现在设计的类多,且不理解原理的话,很难理解源码。一起把这个这么重要的知识点一起理顺吧。
JUC
LockSupport
LockSupport是锁中的基础,是一个提供锁机制的工具类,需要先弄清楚。
public class LockSupport {
//私有构造函数,无法实例化
private LockSupport() {}
private static final sun.misc.Unsafe UNSAFE;
private static final long parkBlockerOffset;
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
parkBlockerOffset = UNSAFE.objectFieldOffset
(tk.getDeclaredField("parkBlocker"));
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception ex) { throw new Error(ex); }
}
}
从源码中可以看到,parkBlocker,threadLocalRandomSeed,threadLocalRandomProbe,threadLocalRandomSecondarySeed都是属于Thread类的,那这些字段到底有啥用,为啥LockSupport要拿出来呢?
parkBlocker的作用
park方法有两种,一种是设置线程字段parkBlocker的方法,另一种是不设置parkBlocker字段的方法
拿park(Object blocker)与park()举例
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
public static void park() {
UNSAFE.park(false, 0L);
}
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
- 底层都是调用Unsafe类的park方法
- 第一种比第二种多了setBlocker方法
- setBlocker方法就是将传入的Object类型的参数赋值给当前线程对象的parkBlocker字段
那有啥用呢?
使用诊断工具可以观察线程被阻塞的原因,诊断工具是通过调用getBlocker(Thread)方法来获取该blocker对象的,所以JDK推荐我们使用带有blocker参数的park方法,并且blocker设置为this,这样当内存dump排查问题时候就能知道是哪个类被阻塞了。
通过代码案例来看:
首先是没有传入参数的park方法
public class LockSupportParkTest {
public static void main(String[] args) {
LockSupport.park();
}
}
结果:
当调用传入参数的park方法
public class LockSupportParkTest {
public static void main(String[] args) {
LockSupportParkTest test = new LockSupportParkTest();
LockSupport.park(test);
}
}
结果:
更深入的理解
Thread.sleep()和Object.wait()的区别
1.Thread.sleep()不会释放占有的锁,Object.wait()会释放占有的锁
解释一下,释放占有的锁是啥意思,占有的锁即 Java内置锁,每个对象都有对象头,对象头的mark word字段都有锁标志,因此每个对象都可以成为锁。
wait会释放对象锁的意思是:我们都知道,wait方法是在同步块/同步方法里面执行,而能进入同步方法块/方法体,即代表已经获取到锁了,而wait是有释放锁资源的作用,可以让其他等待这把锁的线程可以竞争到。
调用了notify方法后,notify并不释放锁,只是告诉调用wait方法的线程,可以去参加锁竞争了;不过锁还可能在别人手里,有可能还没释放;
2. 为什么 wait 方法,notify方法必须在 同步方法块/同步方法体 中呢
调用wait()就是释放锁,释放锁的前提是必须要先获得锁,先获得锁才能释放锁,而进入同步方法块/同步方法体中则说明获得了同步锁了。
notify方法是将锁交给含有wait()方法的线程,让其继续执行下去。
问 notify和wait的原理?
在我的另外一篇文章
Java多线程学习-2https://blog.csdn.net/u012886468/article/details/122498483这里有提到,synchronized的数据结构ObjectMonitor对象,该对象重要属性就包括 EntryList和WaitSet,当 某个拥有ObjectMonitor的线程调用 wait方法,会把该线程放到 waitSet队列当中;
当调用了notify方法后,就会把该线程从waitSet队列移除,放到EntryList中。
3.Thread.sleep()和LockSupport.park()的区别
- 从功能上来说,Thread.sleep()和LockSupport.park()方法类似,都是阻塞当前线程的执行,且都不会释放当前线程占有的锁资源
- Thread.sleep()没法从外部唤醒,只能自己醒过来;LockSupport.park()方法可以被另一个线程调用LockSupport.unpark()方法唤醒;
- Thread.sleep()本身就是一个native方法,LockSupport.park()底层是调用的Unsafe的native方法
- Thread.sleep()方法声明上抛出了InterruptedException中断异常,所以调用者需要捕获这个异常或者再抛出,LockSupport.park()方法不需要捕获中断异常;
4.Thread.sleep()和Condition.await()的区别
Object.wait()和Condition.await()的原理是基本一致的,不同的是Condition.await()底层是调用LockSupport.park()来实现阻塞当前线程的。
实际上,它在阻塞当前线程之前还干了两件事,一是把当前线程添加到条件队列中,二是“完全”释放锁,也就是让state状态变量变为0,然后才是调用LockSupport.park()阻塞当前线程。
Condition
基于Java内置锁实现一种简单的“等待-通知”方式的线程间通信:通过Object对象的wait、notify两类方法。
与Object对象的wait、notify两类方法相类似,基于Lock显式锁,JUC也为大家提供了一个用于线程间进行“等待-通知”方式通信的接口——java.util.concurrent.locks.Condition。
public interface Condition {
/**
* 等待,与Object.wait()语义等效
* 调用thread.interrupt(0则会报错
*/
void await() throws InterruptedException;
/**
* 与await()方法一样,
* 调用thread.interrupt不会报错
*/
void awaitUninterruptibly();
/**
* 与await()方法一样,等待nanosTimeout这么久
*/
long awaitNanos(long nanosTimeout) throws InterruptedException;
/**
* 与await()方法一样,等待time这么久
*/
boolean await(long time, TimeUnit unit) throws InterruptedException;
/**
* 与await()方法一样,等待直到时间为deadline
*/
boolean awaitUntil(Date deadline) throws InterruptedException;
/**
* 唤醒,与Object.notify()语义等效
*/
void signal();
/**
* 唤醒所有,与Object.notifyAll()语义等效
*/
void signalAll();
}
- Condition类的await方法和Object类的wait方法等效
- Condition类的signal方法和Object类的notify方法等效
- Condition类的signalAll方法和Object类的notifyAll方法等效
- Condition对象是基于显式锁的,所以不能独立创建一个Condition对象,而是需要借助于显式锁实例去获取其绑定的Condition对象
- Object的wait()/notify()由JVM底层实现,而Condition接口与实现类完全使用Java代码实现
Condition 对象是通过lock实例获取的,Condition condition = lock.newCondition();
由于Lock有公平锁和非公平锁之分,而Condition是与Lock绑定的,因此就有与Lock一样的公平特性:如果是公平锁,等待线程按照FIFO(先进先出)顺序从Condition对象的等待队列中唤醒;如果是非公平锁,后续的唤醒次序就不保证FIFO顺序了。
AQS
简介
使用基于CAS自旋实现的轻量级锁有两个问题:
- CAS恶性空自旋会浪费大量的CPU资源
- 还有可能会在CPU上导致总线风暴
解决CAS恶性空自旋的有效方式之一是以空间换时间:
- 分散操作热点
- 使用队列削峰
JUC并发包使用的是队列削峰的方案解决CAS的性能问题,并提供了一个基于双向队列的削峰基类——抽象基础类AbstractQueuedSynchronizer(抽象同步器类,简称为AQS)。
AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器;
比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的;
那AQS的数据结构怎么样的?
AQS队列内部维护的是一个FIFO的双向链表,这种结构的特点是每个数据结构都有两个指针,分别指向直接的前驱节点和直接的后继节点。所以双向链表可以从任意一个节点开始很方便地访问前驱节点和后继节点。
每个节点其实是由线程封装的,当线程争抢锁失败后会封装成节点加入AQS队列中;当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。
AQS的内部结构:
AQS的核心成员
AQS出于“分离变与不变”的原则,基于模板模式实现。
模板模式
模板模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。
不同的子类提供不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。
模板模式的关键在于:父类提供框架性的公共逻辑,子类提供个性化的定制逻辑。
1. 模板模式的定义
在模板模式中,由抽象类定义:
- 模板方法
- 钩子方法
模板方法定义一套业务算法框架,算法框架中的某些步骤由钩子方法负责完成。
具体的子类可以按需要重写钩子方法。
模板方法的调用将通过抽象类的实例来完成。
2.模板方法和钩子方法
- 模板方法(Template Method)也常常被称为骨架方法,主要定义了整个方法需要实现的业务操作的算法框架
- 钩子方法(Hook Method)是被模板方法的算法框架所调用的,由子类提供具体的实现方法。在抽象父类中,钩子方法常常被定义为一个空方法或者抽象方法,需要由子类去实现。钩子方法的存在可以让子类提供算法框架中的某个细分操作,从而让子类实现算法中可选的、需要变动的部分。
模板模式在Java开发中用得很多,在很多基础中间件(比如Spring MVC、SpringBoot)中被频繁用到。
AQS中的钩子方法
以上钩子方法的默认实现会抛出UnsupportedOperationException异常。
除了这些钩子方法外,AQS类中的其他方法都是final类型的方法,所以无法被其他类继承,只有这几个方法可以被其他类继承。
状态标志位
AQS中维持了一个单一的volatile修饰的状态信息state,AQS使用int类型的state标示锁的状态,可以理解为锁的同步状态。
/**
* The synchronization state.
* 同步状态,使用 volatile保证线程可见
*/
private volatile int state;
通过volatile保证了可见性,任何线程通过getState()获得状态都可以得到最新值;
而setState()如何保证原子性呢?
答案是通过compareAndSetState()方法利用底层UnSafe的CAS机制来实现原子性;compareAndSetState()方法实际上调用的是unsafe成员的compareAndSwapInt()方法。
来看 state字段作用:
以ReentrantLock为例,state初始化为0,表示未锁定状态。
线程执行该锁的lock()操作时,会调用tryAcquire()方法将state加1。(其他线程再tryAcquire()就会失败,直到A线程unlock()到state=0(释放锁)为止,其他线程才有机会获取该锁)
同一线程可以重复lock(),这样的话state会累加,这就是可重入的概念。不过要注意的是,获得多少次就要释放多少次,要保证state能回零态。
来看下AbstractQueuedSynchronizer(AQS)的继承关系,AbstractQueuedSynchronizer继承了AbstractOwnableSynchronizer,这个基类只有一个变量叫exclusiveOwnerThread,表示当前占用该锁的线程。
还有成员属性:head,tail,state等,我们可以看到 head和tail类型是Node,这个Node怎么样的呢?来看下Node类源码
static final class Node {
// 模式,分为共享与独占
// 共享模式
static final Node SHARED = new Node();
// 独占模式
static final Node EXCLUSIVE = null;
// 结点状态
// CANCELLED,值为1,表示当前的线程被取消
// SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark
// CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中
// PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行
// 值为0,表示当前节点在sync队列中,等待着获取锁
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
// 结点状态
volatile int waitStatus;
// 前驱结点
volatile Node prev;
// 后继结点
volatile Node next;
// 结点所对应的线程
volatile Thread thread;
// 下一个等待者
Node nextWaiter;
// 结点是否在共享模式下等待
final boolean isShared() {
return nextWaiter == SHARED;
}
// 获取前驱结点,若前驱结点为空,抛出异常
final Node predecessor() throws NullPointerException {
// 保存前驱结点
Node p = prev;
if (p == null) // 前驱结点为空,抛出异常
throw new NullPointerException();
else // 前驱结点不为空,返回
return p;
}
// 无参构造方法
Node() { // Used to establish initial head or SHARED marker
}
// 构造方法
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;
}
}
属性总结如下:
FIFO双向同步队列
AQS的内部队列是CLH队列的变种,每当线程通过AQS获取锁失败时,线程将被封装成一个Node节点,通过CAS原子操作插入队列尾部。当有线程释放锁时,AQS会尝试让队头的后继节点占用锁。
AQS通过内置的FIFO双向队列来完成线程的排队工作,内部通过节点head和tail记录队首和队尾元素,元素的节点类型为Node类型。
/**
* Head of the wait queue, lazily initialized. Except for
* initialization, it is modified only via method setHead. Note:
* If head exists, its waitStatus is guaranteed not to be
* CANCELLED.
*/
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
private transient volatile Node tail;
- AQS的首节点和尾节点都是懒加载的。懒加载的意思是在需要的时候才真正创建。
- 只有在线程竞争失败的情况下,有新线程加入同步队列时,AQS才创建一个head节点
- head节点只能被setHead()方法修改,并且节点的waitStatus不能为CANCELLED
- 尾节点只在有新线程阻塞时才被创建
AQS对资源的共享方式
- Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁
- 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
- 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
- Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。
AQS源码分析
Node类
上面有相关源码,这里总结一下。
每个线程被阻塞的线程都会被封装成一个Node结点,放入队列。每个节点包含了一个Thread类型的引用,并且每个节点都存在一个状态。
状态为:
-
CANCELLED
,值为1,表示当前的线程被取消。 -
SIGNAL
,值为-1,表示当前节点的后继节点包含的线程需要运行,需要进行unpark操作。 -
CONDITION
,值为-2,表示当前节点在等待condition,也就是在condition queue中。 -
PROPAGATE
,值为-3,表示当前场景下后续的acquireShared能够得以执行。 -
值为0,表示当前节点在sync queue中,等待着获取锁。
ConditionObject类
// 内部类
public class ConditionObject implements Condition, java.io.Serializable {
// 版本号
private static final long serialVersionUID = 1173984872572414699L;
/** First node of condition queue. */
// condition队列的头节点
private transient Node firstWaiter;
/** Last node of condition queue. */
// condition队列的尾结点
private transient Node lastWaiter;
/**
* Creates a new {@code ConditionObject} instance.
*/
// 构造方法
public ConditionObject() { }
// Internal methods
/**
* Adds a new waiter to wait queue.
* @return its new wait node
*/
// 添加新的waiter到wait队列
private Node addConditionWaiter() {
// 保存尾结点
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) { // 尾结点不为空,并且尾结点的状态不为CONDITION
// 清除状态为CONDITION的结点
unlinkCancelledWaiters();
// 将最后一个结点重新赋值给t
t = lastWaiter;
}
// 新建一个结点
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null) // 尾结点为空
// 设置condition队列的头节点
firstWaiter = node;
else // 尾结点不为空
// 设置为节点的nextWaiter域为node结点
t.nextWaiter = node;
// 更新condition队列的尾结点
lastWaiter = node;
return node;
}
/**
* Removes and transfers nodes until hit non-cancelled one or
* null. Split out from signal in part to encourage compilers
* to inline the case of no waiters.
* @param first (non-null) the first node on condition queue
*/
private void doSignal(Node first) {
// 循环
do {
if ( (firstWaiter = first.nextWaiter) == null) // 该节点的nextWaiter为空
// 设置尾结点为空
lastWaiter = null;
// 设置first结点的nextWaiter域
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null); // 将结点从condition队列转移到sync队列失败并且condition队列中的头节点不为空,一直循环
}
/**
* Removes and transfers all nodes.
* @param first (non-null) the first node on condition queue
*/
private void doSignalAll(Node first) {
// condition队列的头节点尾结点都设置为空
lastWaiter = firstWaiter = null;
// 循环
do {
// 获取first结点的nextWaiter域结点
Node next = first.nextWaiter;
// 设置first结点的nextWaiter域为空
first.nextWaiter = null;
// 将first结点从condition队列转移到sync队列
transferForSignal(first);
// 重新设置first
first = next;
} while (first != null);
}
/**
* Unlinks cancelled waiter nodes from condition queue.
* Called only while holding lock. This is called when
* cancellation occurred during condition wait, and upon
* insertion of a new waiter when lastWaiter is seen to have
* been cancelled. This method is needed to avoid garbage
* retention in the absence of signals. So even though it may
* require a full traversal, it comes into play only when
* timeouts or cancellations occur in the absence of
* signals. It traverses all nodes rather than stopping at a
* particular target to unlink all pointers to garbage nodes
* without requiring many re-traversals during cancellation
* storms.
*/
// 从condition队列中清除状态为CANCEL的结点
private void unlinkCancelledWaiters() {
// 保存condition队列头节点
Node t = firstWaiter;
Node trail = null;
while (t != null) { // t不为空
// 下一个结点
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) { // t结点的状态不为CONDTION状态
// 设置t节点的额nextWaiter域为空
t.nextWaiter = null;
if (trail == null) // trail为空
// 重新设置condition队列的头节点
firstWaiter = next;
else // trail不为空
// 设置trail结点的nextWaiter域为next结点
trail.nextWaiter = next;
if (next == null) // next结点为空
// 设置condition队列的尾结点
lastWaiter = trail;
}
else // t结点的状态为CONDTION状态
// 设置trail结点
trail = t;
// 设置t结点
t = next;
}
}
// public methods
/**
* Moves the longest-waiting thread, if one exists, from the
* wait queue for this condition to the wait queue for the
* owning lock.
*
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
// 唤醒一个等待线程。如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返回之前,该线程必须重新获取锁。
public final void signal() {
if (!isHeldExclusively()) // 不被当前线程独占,抛出异常
throw new IllegalMonitorStateException();
// 保存condition队列头节点
Node first = firstWaiter;
if (first != null) // 头节点不为空
// 唤醒一个等待线程
doSignal(first);
}
/**
* Moves all threads from the wait queue for this condition to
* the wait queue for the owning lock.
*
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
// 唤醒所有等待线程。如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁。
public final void signalAll() {
if (!isHeldExclusively()) // 不被当前线程独占,抛出异常
throw new IllegalMonitorStateException();
// 保存condition队列头节点
Node first = firstWaiter;
if (first != null) // 头节点不为空
// 唤醒所有等待线程
doSignalAll(first);
}
/**
* Implements uninterruptible condition wait.
* <ol>
* <li> Save lock state returned by {@link #getState}.
* <li> Invoke {@link #release} with saved state as argument,
* throwing IllegalMonitorStateException if it fails.
* <li> Block until signalled.
* <li> Reacquire by invoking specialized version of
* {@link #acquire} with saved state as argument.
* </ol>
*/
// 等待,当前线程在接到信号之前一直处于等待状态,不响应中断
public final void awaitUninterruptibly() {
// 添加一个结点到等待队列
Node node = addConditionWaiter();
// 获取释放的状态
int savedState = fullyRelease(node);
boolean interrupted = false;
while (!isOnSyncQueue(node)) { //
// 阻塞当前线程
LockSupport.park(this);
if (Thread.interrupted()) // 当前线程被中断
// 设置interrupted状态
interrupted = true;
}
if (acquireQueued(node, savedState) || interrupted) //
selfInterrupt();
}
/*
* For interruptible waits, we need to track whether to throw
* InterruptedException, if interrupted while blocked on
* condition, versus reinterrupt current thread, if
* interrupted while blocked waiting to re-acquire.
*/
/** Mode meaning to reinterrupt on exit from wait */
private static final int REINTERRUPT = 1;
/** Mode meaning to throw InterruptedException on exit from wait */
private static final int THROW_IE = -1;
/**
* Checks for interrupt, returning THROW_IE if interrupted
* before signalled, REINTERRUPT if after signalled, or
* 0 if not interrupted.
*/
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
/**
* Throws InterruptedException, reinterrupts current thread, or
* does nothing, depending on mode.
*/
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
/**
* Implements interruptible condition wait.
* <ol>
* <li> If current thread is interrupted, throw InterruptedException.
* <li> Save lock state returned by {@link #getState}.
* <li> Invoke {@link #release} with saved state as argument,
* throwing IllegalMonitorStateException if it fails.
* <li> Block until signalled or interrupted.
* <li> Reacquire by invoking specialized version of
* {@link #acquire} with saved state as argument.
* <li> If interrupted while blocked in step 4, throw InterruptedException.
* </ol>
*/
// // 等待,当前线程在接到信号或被中断之前一直处于等待状态
public final void await() throws InterruptedException {
if (Thread.interrupted()) // 当前线程被中断,抛出异常
throw new InterruptedException();
// 在wait队列上添加一个结点
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);
}
/**
* Implements timed condition wait.
* <ol>
* <li> If current thread is interrupted, throw InterruptedException.
* <li> Save lock state returned by {@link #getState}.
* <li> Invoke {@link #release} with saved state as argument,
* throwing IllegalMonitorStateException if it fails.
* <li> Block until signalled, interrupted, or timed out.
* <li> Reacquire by invoking specialized version of
* {@link #acquire} with saved state as argument.
* <li> If interrupted while blocked in step 4, throw InterruptedException.
* </ol>
*/
// 等待,当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态
public final long awaitNanos(long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
final long deadline = System.nanoTime() + nanosTimeout;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (nanosTimeout <= 0L) {
transferAfterCancelledWait(node);
break;
}
if (nanosTimeout >= spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
nanosTimeout = deadline - System.nanoTime();
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return deadline - System.nanoTime();
}
/**
* Implements absolute timed condition wait.
* <ol>
* <li> If current thread is interrupted, throw InterruptedException.
* <li> Save lock state returned by {@link #getState}.
* <li> Invoke {@link #release} with saved state as argument,
* throwing IllegalMonitorStateException if it fails.
* <li> Block until signalled, interrupted, or timed out.
* <li> Reacquire by invoking specialized version of
* {@link #acquire} with saved state as argument.
* <li> If interrupted while blocked in step 4, throw InterruptedException.
* <li> If timed out while blocked in step 4, return false, else true.
* </ol>
*/
// 等待,当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态
public final boolean awaitUntil(Date deadline)
throws InterruptedException {
long abstime = deadline.getTime();
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
boolean timedout = false;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (System.currentTimeMillis() > abstime) {
timedout = transferAfterCancelledWait(node);
break;
}
LockSupport.parkUntil(this, abstime);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return !timedout;
}
/**
* Implements timed condition wait.
* <ol>
* <li> If current thread is interrupted, throw InterruptedException.
* <li> Save lock state returned by {@link #getState}.
* <li> Invoke {@link #release} with saved state as argument,
* throwing IllegalMonitorStateException if it fails.
* <li> Block until signalled, interrupted, or timed out.
* <li> Reacquire by invoking specialized version of
* {@link #acquire} with saved state as argument.
* <li> If interrupted while blocked in step 4, throw InterruptedException.
* <li> If timed out while blocked in step 4, return false, else true.
* </ol>
*/
// 等待,当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。此方法在行为上等效于: awaitNanos(unit.toNanos(time)) > 0
public final boolean await(long time, TimeUnit unit)
throws InterruptedException {
long nanosTimeout = unit.toNanos(time);
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
final long deadline = System.nanoTime() + nanosTimeout;
boolean timedout = false;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (nanosTimeout <= 0L) {
timedout = transferAfterCancelledWait(node);
break;
}
if (nanosTimeout >= spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
nanosTimeout = deadline - System.nanoTime();
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return !timedout;
}
// support for instrumentation
/**
* Returns true if this condition was created by the given
* synchronization object.
*
* @return {@code true} if owned
*/
final boolean isOwnedBy(AbstractQueuedSynchronizer sync) {
return sync == AbstractQueuedSynchronizer.this;
}
/**
* Queries whether any threads are waiting on this condition.
* Implements {@link AbstractQueuedSynchronizer#hasWaiters(ConditionObject)}.
*
* @return {@code true} if there are any waiting threads
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
// 查询是否有正在等待此条件的任何线程
protected final boolean hasWaiters() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
for (Node w = firstWaiter; w != null; w = w.nextWaiter) {
if (w.waitStatus == Node.CONDITION)
return true;
}
return false;
}
/**
* Returns an estimate of the number of threads waiting on
* this condition.
* Implements {@link AbstractQueuedSynchronizer#getWaitQueueLength(ConditionObject)}.
*
* @return the estimated number of waiting threads
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
// 返回正在等待此条件的线程数估计值
protected final int getWaitQueueLength() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int n = 0;
for (Node w = firstWaiter; w != null; w = w.nextWaiter) {
if (w.waitStatus == Node.CONDITION)
++n;
}
return n;
}
/**
* Returns a collection containing those threads that may be
* waiting on this Condition.
* Implements {@link AbstractQueuedSynchronizer#getWaitingThreads(ConditionObject)}.
*
* @return the collection of threads
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
// 返回包含那些可能正在等待此条件的线程集合
protected final Collection<Thread> getWaitingThreads() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
ArrayList<Thread> list = new ArrayList<Thread>();
for (Node w = firstWaiter; w != null; w = w.nextWaiter) {
if (w.waitStatus == Node.CONDITION) {
Thread t = w.thread;
if (t != null)
list.add(t);
}
}
return list;
}
}
此类实现了Condition接口,Condition接口定义了条件操作规范,具体如下:
public interface Condition {
// 等待,当前线程在接到信号或被中断之前一直处于等待状态
void await() throws InterruptedException;
// 等待,当前线程在接到信号之前一直处于等待状态,不响应中断
void awaitUninterruptibly();
//等待,当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态
long awaitNanos(long nanosTimeout) throws InterruptedException;
// 等待,当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。此方法在行为上等效于: awaitNanos(unit.toNanos(time)) > 0
boolean await(long time, TimeUnit unit) throws InterruptedException;
// 等待,当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态
boolean awaitUntil(Date deadline) throws InterruptedException;
// 唤醒一个等待线程。如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返回之前,该线程必须重新获取锁。
void signal();
// 唤醒所有等待线程。如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁。
void signalAll();
}
Condition接口中定义了await、signal方法,用来等待条件、释放条件。
一个锁中,可以创建多个等待任务队列,这点和内置锁不同,Java内置锁上只有唯一的一个等待队列。
Lock lock = new SimpleLock();
Condition firstCondition = lock.newCondition();
Condition secondCondition = lock.newCondition();
await方法
当线程调用await()方法时,说明当前线程的节点为当前AQS队列的头节点,正好处于占有锁的状态,await()方法需要把该线程从AQS队列挪到Condition等待队列里面。
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);
}
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
await()流程:
- 执行await()时,会新创建一个节点并放入Condition队列尾部
- 然后释放锁,并唤醒AQS同步队列中的头节点的后一个节点
- 然后执行while循环,将该节点的线程阻塞,直到该节点离开等待队列,重新回到同步队列成为同步节点后,线程才退出while循环
- 退出循环后,开始调用acquireQueued()不断尝试拿锁
- 拿到锁后,会清空Condition队列中被取消的节点
signal方法唤醒原理
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
signal()方法流程:
- 通过enq()方法自旋将条件队列中的头节点放入AQS同步队列尾部,并获取它在AQS队列中的前驱节点。
- 如果前驱节点的状态是取消状态,或者设置前驱节点为Signal状态失败,就唤醒当前节点的线程;否则节点在同步队列的尾部,参与排队
- 同步队列中的线程被唤醒后,表示重新获取了显式锁,然后继续执行condition.await()语句后面的临界区代码。
类的核心方法
acquire方法
该方法以独占模式获取(资源),忽略中断,即线程在aquire过程中,中断此线程是无效的。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- acquire(arg)至少执行一次tryAcquire(arg)钩子方法
- 若调用tryAcquire(arg)尝试成功,则acquire()将直接返回,表示已经抢到锁;若不成功,则将线程加入等待队列
- 在acquire模板方法中,如果钩子方法tryAcquire尝试获取同步状态失败的话,就构造同步节点(独占式节点模式为Node.EXCLUSIVE),通过addWaiter(Nodenode,int args)方法将该节点加入同步队列的队尾
- addWaiter()第一次尝试在尾部添加节点失败,意味着有并发抢锁发生,需要进行自旋。enq()方法通过CAS自旋将节点添加到队列尾部。
- 在节点入队之后,启动自旋抢锁的流程
- acquireQueued()方法的主要逻辑:当前Node节点线程在死循环中不断获取同步状态,并且不断在前驱节点上自旋,只有当前驱节点是头节点时才能尝试获取锁
- 为了不浪费资源,acquireQueued()自旋过程中会阻塞线程,等待被前驱节点唤醒后才启动循环。
- 如果成功就返回,否则执行shouldParkAfterFailedAcquire()、parkAndCheckInterrupt()来达到阻塞的效果
- 如果头节点获取了锁,那么该节点绑定的线程会终止acquireQueued()自旋,线程会去执行临界区代码。此时,其余的节点处于自旋状态,处于自旋状态的线程当然也不会执行无效的空循环而导致CPU资源浪费,而是被挂起(Park)进入阻塞状态。
说明:
- 首先,执行tryAcquire()方法是 在独占模式下获取对象状态。并且在AQS中默认抛出一个异常,这样是为了不能直接调用该方法,需要子类去重写此方法,实现自己的逻辑。
- tryAcquire()方法不成功说明获取资源失败,那既然失败,就得添加等待者然后放进队列中然后不断重试获取资源。因此先调用addWaiter方法(新增一个等待者),再把资源放进队列中不断排队获取资源。
接下来先看 addWaiter()方法
// 添加等待者
private Node addWaiter(Node mode) {
// 新生成一个结点,默认为独占模式
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域连接到尾结点
node.prev = pred;
if (compareAndSetTail(pred, node)) { // 比较pred是否为尾结点,是则将尾结点设置为node
// 设置尾结点的next域为node
pred.next = node;
return node; // 返回新生成的结点
}
}
enq(node); // 尾结点为空(即还没有被初始化过),或者是compareAndSetTail操作失败,则入队列
return node;
}
private Node enq(final Node node) {
for (;;) { // 无限循环,确保结点能够成功入队列
// 保存尾结点
Node t = tail;
if (t == null) { // 尾结点为空,即还没被初始化
if (compareAndSetHead(new Node())) // 头节点为空,并设置头节点为新生成的结点
tail = head; // 头节点与尾结点都指向同一个新生结点
} else { // 尾结点不为空,即已经被初始化过
// 将node结点的prev域连接到尾结点
node.prev = t;
if (compareAndSetTail(t, node)) { // 比较结点t是否为尾结点,若是则将尾结点设置为node
// 设置尾结点的next域为node
t.next = node;
return t; // 返回尾结点
}
}
}
}
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
addWaiter流程图如下:
现在,分析acquireQueue方法
// sync队列中的结点在独占且忽略中断的模式下获取(资源)
final boolean acquireQueued(final Node node, int arg) {
// 标志
boolean failed = true;
try {
// 中断标志
boolean interrupted = false;
for (;;) { // 无限循环
// 获取node节点的前驱结点
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);
}
}
// 当获取(资源)失败后,检查并且更新结点状态
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 获取前驱结点的状态
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) // 状态为SIGNAL,为-1
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
// 可以进行park操作
return true;
if (ws > 0) { // 表示状态为CANCELLED,为1
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0); // 找到pred结点前面最近的一个状态不为CANCELLED的结点
// 赋值pred结点的next域
pred.next = node;
} else { // 为PROPAGATE -3 或者是0 表示无状态,(为CONDITION -2时,表示此节点在condition queue中)
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
// 比较并设置前驱结点的状态为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
// 不能进行park操作
return false;
}
// 进行park操作并且返回该线程是否被中断
private final boolean parkAndCheckInterrupt() {
// 在许可可用之前禁用当前线程,并且设置了blocker
LockSupport.park(this);
return Thread.interrupted(); // 当前线程是否已被中断,并清除中断标记位
}
// 取消继续获取(资源)
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
// node为空,返回
if (node == null)
return;
// 设置node结点的thread为空
node.thread = null;
// Skip cancelled predecessors
// 保存node的前驱结点
Node pred = node.prev;
while (pred.waitStatus > 0) // 找到node前驱结点中第一个状态小于0的结点,即不为CANCELLED状态的结点
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
// 获取pred结点的下一个结点
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
// 设置node结点的状态为CANCELLED
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
if (node == tail && compareAndSetTail(node, pred)) { // node结点为尾结点,则设置尾结点为pred结点
// 比较并设置pred结点的next节点为null
compareAndSetNext(pred, predNext, null);
} else { // node结点不为尾结点,或者比较设置不成功
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) { // (pred结点不为头节点,并且pred结点的状态为SIGNAL)或者
// pred结点状态小于等于0,并且比较并设置等待状态为SIGNAL成功,并且pred结点所封装的线程不为空
// 保存结点的后继
Node next = node.next;
if (next != null && next.waitStatus <= 0) // 后继不为空并且后继的状态小于等于0
compareAndSetNext(pred, predNext, next); // 比较并设置pred.next = next;
} else {
unparkSuccessor(node); // 释放node的前一个结点
}
node.next = node; // help GC
}
}
// 释放后继结点
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
// 获取node结点的等待状态
int ws = node.waitStatus;
if (ws < 0) // 状态值小于0,为SIGNAL -1 或 CONDITION -2 或 PROPAGATE -3
// 比较并且设置结点等待状态,设置为0
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
// 获取node节点的下一个结点
Node s = node.next;
if (s == null || s.waitStatus > 0) { // 下一个结点为空或者下一个节点的等待状态大于0,即为CANCELLED
// s赋值为空
s = null;
// 从尾结点开始从后往前开始遍历
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0) // 找到等待状态小于等于0的结点,找到最前的状态小于等于0的结点
// 保存结点
s = t;
}
if (s != null) // 该结点不为为空,释放许可
LockSupport.unpark(s.thread);
}
流程图如下:
总结一下:
- 调用ReentrantLock.lock方法即相当于调用AQS的acquire方法
- acquire(arg)至少执行一次tryAcquire(arg)钩子方法
- 若调用tryAcquire(arg)尝试成功,则acquire()将直接返回,表示已经抢到锁;若不成功,则将线程加入等待队列
- 如果钩子方法tryAcquire尝试获取同步状态失败的话,就构造同步节点(独占式节点模式为Node.EXCLUSIVE),通过addWaiter(Nodenode,int args)方法将该节点加入同步队列的队尾。
- addWaiter()第一次尝试在尾部添加节点失败,意味着有并发抢锁发生,需要进行自旋。enq()方法通过CAS自旋将节点添加到队列尾部
- 在节点入队之后,启动自旋抢锁的流程。acquireQueued()方法的主要逻辑:当前Node节点线程在死循环中不断获取同步状态,并且不断在前驱节点上自旋,只有当前驱节点是头节点时才能尝试获取锁
- 为了不浪费资源,acquireQueued()自旋过程中会阻塞线程,等待被前驱节点唤醒后才启动循环。如果成功就返回,否则执行shouldParkAfterFailedAcquire()、parkAndCheckInterrupt()来达到阻塞的效果
- acquireQueued()自旋在阻塞自己的线程之前会进行挂起预判。
- shouldParkAfterFailedAcquire()方法的主要功能是:将当前节点的有效前驱节点(是指有效节点不是CANCELLED类型的节点)找到,并且将有效前驱节点的状态设置为SIGNAL,之后返回true代表当前线程可以马上被阻塞了。具体可以分为三种情况:
- 如果前驱节点的状态为-1(SIGNAL),说明前驱的等待标志已设好,返回true表示设置完毕。
- 如果前驱节点的状态为1(CANCELLED),说明前驱节点本身不再等待了,需要跨越这些节点,然后找到一个有效节点,再把当前节点和这个有效节点的唤醒
- 如果是其他情况:-3(PROPAGATE,共享锁等待)、-2(CONDITION,条件等待)、0(初始状态),那么通过CAS尝试设置前驱节点为SIGNAL
- parkAndCheckInterrupt()的主要任务是暂停当前线程
AQS的入队和出队
节点入队代码在 enq方法中
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;
}
}
}
}
如果AQS的队列为空,新节点入队时,AQS通过CAS方法将新节点设置为头节点head,并且将tail指针指向新节点
节点出队的算法在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);
}
}
acquireQueued()方法不断在前驱节点上自旋(for死循环),如果前驱节点是头节点并且当前线程使用钩子方法tryAcquire(arg)获得了锁,就移除头节点,将当前节点设置为头节点。
release方法
public final boolean release(int arg) {
if (tryRelease(arg)) { // 释放成功
// 保存头节点
Node h = head;
if (h != null && h.waitStatus != 0) // 头节点不为空并且头节点状态不为0
unparkSuccessor(h); //释放头节点的后继结点
return true;
}
return false;
}
tryRelease的默认实现是抛出异常,需要具体的子类实现,如果tryRelease成功,那么如果头节点不为空并且头节点的状态不为0,则释放头节点的后继结点。
来看unparkSuccessor方法
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;
}
if (s != null)
LockSupport.unpark(s.thread);
}
- 把节点状态通过CAS更改为0
- 找到下一个节点,如果不为空,则唤醒该节点的线程。
lock
调用链如下
unlock
ReentrantLock
概念
ReentrantLock是一个可重入的互斥锁,又称为“可重入独占锁”。
ReentrantLock锁在同一个时间点只能被一个线程锁持有,而可重入的意思是,ReentrantLock锁可以被单个线程多次获取。
ReentrantLock把所有Lock接口的操作都委派到一个Sync类上,该类继承了AbstractQueuedSynchronizer
NonfairSync为非公平(或者不公平)同步器,FairSync为公平同步器,ReentrantLock默认为非公平。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
可以说 AQS 和ReentrantLock是组合关系。
非公平抢锁流程
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
首先用一个CAS操作判断state是不是0(表示当前锁未被占用),如果是0就把它置为1,并且设置当前线程为该锁的独占线程,表示获取锁成功。当多个线程同时尝试占用同一个锁时,CAS操作只能保证一个线程操作成功,剩下的只能乖乖去排队。
ReentrantLock“非公平”性体现在这里:如果占用锁的线程刚释放锁,state置为0,而排队等待锁的线程还未唤醒,新来的线程就直接抢占了该锁,那么就“插队”了。
非公平抢占的钩子方法:tryAcquire(arg)
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;
}
非公平同步器ReentrantLock.NonfairSync的核心思想是当前线程尝试获取锁的时候,如果发现锁的状态位是0,就直接尝试将锁拿过来,然后执行setExclusiveOwnerThread(),根本不管同步队列中的排队节点。
公平锁的抢占流程
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
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;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
公平同步器ReentrantLock.FairSync的核心思想是通过AQS模板方法进行队列入队操作。
tryAcquire(arg)方法
公平抢占的钩子方法中,首先判断是否有后继节点,如果有后继节点,并且当前线程不是锁的占有线程,钩子方法就返回false,模板方法会进入排队的执行流程,可见公平锁是真正公平的。
AQS的实际应用
AQS建立在CAS原子操作和volatile可见性变量的基础之上,为上层的显式锁、同步工具类、阻塞队列、线程池、并发容器、Future异步工具提供线程之间同步的基础设施。所以,AQS在JUC框架中的使用是非常广泛的。
未完待续,我会在下篇博文继续介绍JUC相关的技术,敬请期待。