高并发的情况下,i++
无法保证原子性,往往会出现问题,所以引入AtomicInteger
类。
我们分别累加普通变量、volatile变量、AtomicInteger变量
public class TestAtomicInteger {
private static final int THREADS_COUNT = 2;
public static int count = 0;
public static volatile int countVolatile = 0;
public static AtomicInteger atomicInteger = new AtomicInteger(0);
public static CountDownLatch countDownLatch = new CountDownLatch(2);
public static void increase() {
count++;
countVolatile++;
atomicInteger.incrementAndGet();
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[THREADS_COUNT];
for(int i = 0; i< threads.length; i++) {
threads[i] = new Thread(() -> {
for(int i1 = 0; i1 < 1000; i1++) {
increase();
}
countDownLatch.countDown();
});
threads[i].start();
}
// 等线程全部算完再输出
countDownLatch.await();
// 正确值2000
System.out.println(count);//普通变量 输出1977
System.out.println(countVolatile);//volatile变量 输出1990
System.out.println(atomicInteger.get());//AtomicInteger变量 输出2000
}
}
package java.util.concurrent.atomic;
import java.util.function.IntUnaryOperator;
import java.util.function.IntBinaryOperator;
import sun.misc.Unsafe;
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// 使用Unsafe类的CAS去更新值:Unsafe.compareAndSwapInt
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
// 通过Unsafe计算出AtomicInteger类的value属性在对象中的偏移,该偏移值下边会用到
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// 被volatile修饰
private volatile int value;
public AtomicInteger(int initialValue) {
value = initialValue;
}
public AtomicInteger() {//初始值为0
}
public final int get() {
return value;
}
public final void set(int newValue) {
value = newValue;
}
/**
1.首先set()是对volatile变量的一个写操作, 我们知道volatile的write为了保证对其他线程的可见性会追加以下两个Fence(内存屏障)
1)StoreStore // 在intel cpu中, 不存在[写写]重排序, 这个可以直接省略了
2)StoreLoad // 这个是所有内存屏障里最耗性能的
注: 内存屏障相关参考Doug Lea大大的cookbook (http://g.oswego.edu/dl/jmm/cookbook.html)
2.Doug Lea大大又说了, lazySet()省去了StoreLoad屏障, 只留下StoreStore
*/
// 调用unsafe.putOrderedInt
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
// 设置新值,并返回旧值
public final int getAndSet(int newValue) {
// 用unsafe类进行操作
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* May fail
* spuriously and does not provide ordering guarantees, so is
* only rarely an appropriate alternative to {@code compareAndSet}.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful
*/
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
/*
采用CAS机制,不断使用compareAndSwapInt尝试修改该值,如果失败,重新获取。如果并发量小,问题不大。
并发量大的情况下,由于真正更新成功的线程占少数,容易导致循环次数过多,浪费时间。
由于需要保证变量真正的共享,缓存行失效,缓存一致性开销变大。
底层开销可能较大,这个我就不追究了。
该函数做的事较多,不仅增加value,同时还给出返回值,返回值换成void就好了。
*/
public final int getAndIncrement() {//返回旧值
return unsafe.getAndAddInt(this, valueOffset, 1);
}
/*
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {//从主内存拿最新的值赋值给v5
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
*/
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
public final int incrementAndGet() {//返回新值
// this是调用的变量,valueOffset主内存的数的地址,1是+1
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
/**
* Atomically updates the current value with the results of
* applying the given function, returning the previous value. The
* function should be side-effect-free, since it may be re-applied
* when attempted updates fail due to contention among threads.
*
* @param updateFunction a side-effect-free function
*/
// 该方法需要实现IntUnaryOperator接口,然后会调用applyAsInt方法对当前值进行处理,将当前值替换为applyAsInt方法的返回值。
public final int getAndUpdate(IntUnaryOperator updateFunction) {//返回旧值
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return prev;
}
/**
* Atomically updates the current value with the results of
* applying the given function, returning the updated value. The
* function should be side-effect-free, since it may be re-applied
* when attempted updates fail due to contention among threads.
*
* @param updateFunction a side-effect-free function
*/
public final int updateAndGet(IntUnaryOperator updateFunction) {//返回新值
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return next;
}
/**
* Atomically updates the current value with the results of
* applying the given function to the current and given values,
* returning the previous value. The function should be
* side-effect-free, since it may be re-applied when attempted
* updates fail due to contention among threads. The function
* is applied with the current value as its first argument,
* and the given update as the second argument.
*
* @param x the update value
* @param accumulatorFunction a side-effect-free function of two arguments
*/
// 返回旧值
public final int getAndAccumulate(int x,//更新值
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return prev;
}
/**
* Atomically updates the current value with the results of
* applying the given function to the current and given values,
* returning the updated value. The function should be
* side-effect-free, since it may be re-applied when attempted
* updates fail due to contention among threads. The function
* is applied with the current value as its first argument,
* and the given update as the second argument.
*
* @param x the update value
* @param accumulatorFunction a side-effect-free function of two arguments
* @return the updated value
*/
public final int accumulateAndGet(int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return next;
}
public String toString() {
return Integer.toString(get());
}
public int intValue() {
return get();
}
public long longValue() {
return (long)get();
}
public float floatValue() {
return (float)get();
}
public double doubleValue() {
return (double)get();
}
}
先去内存拿最新的地址,然后不断进行CAS直到成功
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {//从主内存拿最新的值赋值给v5
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));//compareAndSwapInt是原子性的操作:比较和替换是连着的,不可分割,不会分割成比较好后被别的线程插入更改
return var5;
}
public class UseAtomicReference {
// 保证User的原子性
static AtomicReference<User> userRef = new AtomicReference<User>();
public static void main(String[] args) {
User zhangsan = new User("zhangsan", 15);
userRef.set(zhangsan);
User lisi = new User("lisi", 11);
// 不是更新的zhangsan本身
userRef.compareAndSet(zhangsan,lisi);
System.out.println(userRef.get().getName());//lisi
System.out.println(userRef.get().getAge());
System.out.println(zhangsan.name);//zhangsan
System.out.println(zhangsan.age);
}
}
class User{
public User(String name, int age) {
this.name = name;
this.age = age;
}
String name;
int age;
public int getAge() {
return age;
}
public String getName() {
return name;
}
}
atomic
处理CAS的ABA问题有两个可用的类
解决CAS的ABA问题
package a;
// 处理CAS的ABA问题有两个可用的类
// AtomicMarkableReference boolean有没有人动过
// AtomicStampedReference 被动过几次
import java.util.concurrent.atomic.AtomicStampedReference;
public class TestAtomicStampedReference {
static AtomicStampedReference<String> asr = new AtomicStampedReference<>("zhangsan", 0); //0是版本号
public static void main(String[] args) throws InterruptedException {
String oldReference = asr.getReference(); // 初始的原值
int oldStamp = asr.getStamp(); // 初始的版本号
System.out.println("初始值" + oldReference + "======版本号" + oldStamp);
Thread lisi =
new Thread(
new Runnable() {
@Override
public void run() {
System.out.println(
Thread.currentThread().getName()
+ " lisi线程 cas执行前 以为当前变量值:"
+ oldReference
+ " 以为当前版本号"
+ oldStamp
+ " 尝试cas:"
+ asr.compareAndSet(oldReference, "lisi", oldStamp, oldStamp + 1)
+ " cas后当前值为"
+ asr.getReference()
+ " 版本号为"
+ asr.getStamp());
}
});
Thread wangwu =
new Thread(
new Runnable() {
@Override
public void run() {
String reference = asr.getReference();
System.out.println(
Thread.currentThread().getName()
+ " wangwu线程 cas执行前 以为当前变量值:"
+ oldReference
+ " 以为当前版本号"
+ oldStamp
+ " 尝试cas:"
+ asr.compareAndSet(oldReference, "wangwu", oldStamp, oldStamp + 1)
+ " cas后当前值为"
+ asr.getReference()
+ " 版本号为"
+ asr.getStamp());
}
});
// 让lisi先执行,且用join确保lisi执行完了wangwu再执行
lisi.start();
lisi.join();
wangwu.start();
wangwu.join();
System.out.println("最后值和版本号" + asr.getReference() + "======" + asr.getStamp());
}
}
/*
初始值zhangsan======版本号0
Thread-0 lisi线程 cas执行前 以为当前变量值:zhangsan 以为当前版本号0 尝试cas:true cas后当前值为lisi 版本号为1
Thread-1 wangwu线程 cas执行前 以为当前变量值:zhangsan 以为当前版本号0 尝试cas:false cas后当前值为lisi 版本号为1
最后值和版本号lisi======1
*/
package a;
import java.util.concurrent.atomic.AtomicIntegerArray;
public class TestAtomicArray {
static int[] arr = new int[] {1, 2};
static AtomicIntegerArray ai = new AtomicIntegerArray(arr);
public static void main(String[] args) {
// 把索引为1的值改为3
ai.getAndSet(0, 3);
System.out.println(ai.get(0));//3
System.out.println(arr[0]);//1
}
}
在JAVA中,sun.misc.Unsafe
类提供了硬件级别的原子操作来实现这个CAS。 java.util.concurrent
包下的大量类都使用了这个 Unsafe.java
类的CAS操作。
java.util.concurrent.atomic
包下的类大多是使用CAS操作来实现的(如 AtomicInteger.java
,AtomicBoolean
,AtomicLong
)。下面以 AtomicInteger.java
的部分实现来大致讲解下这些原子类的实现。
一般来说在竞争不是特别激烈的时候,使用该包下的原子操作性能比使用 synchronized 关键字的方式高效的多(查看getAndSet(),可知如果资源竞争十分激烈的话,这个for循环可能换持续很久都不能成功跳出。不过这种情况可能需要考虑降低资源竞争才是)。
在较多的场景我们都可能会使用到这些原子类操作。一个典型应用就是计数了,在多线程的情况下需要考虑线程安全问题。通常第一映像可能就是:
public class Counter {
private int count;
public Counter(){}
public int getCount(){
return count;
}
public void increase(){
count++;
}
}
上面这个类在多线程环境下会有线程安全问题,要解决这个问题最简单的方式可能就是通过加锁的方式,调整如下:
public class Counter {
private int count;
public Counter(){}
public synchronized int getCount(){
return count;
}
public synchronized void increase(){
count++;
}
}
这类似于悲观锁的实现,我需要获取这个资源,那么我就给他加锁,别的线程都无法访问该资源,直到我操作完后释放对该资源的锁。我们知道,悲观锁的效率是不如乐观锁的,上面说了Atomic下的原子类的实现是类似乐观锁的,效率会比使用 synchronized关键字高,推荐使用这种方式,实现如下:
public class Counter {
private AtomicInteger count = new AtomicInteger();
public Counter(){}
public int getCount(){
return count.get();
}
public void increase(){
count.getAndIncrement();
}
}
但下面的自旋也许能给你提供另一种思路
volatile int status=0;//标识--是否有线程再同步块---是否有线程上锁成功
void lock(){
while(!compareAndSet(0,1)){
//本来希望的oldvalue是0,但是被其他线程修改为了1,所以当前线程就一个劲等,等到他被其他线程是否了后其他线程修改为0,当前线程就能拿到锁跳出循环了
}
//加锁成功
}
void unlock(){
status=0;
}
boolean compareAndSet(int except,int newValue){
//cas操作,修改status成功则返回true
}
缺点:耗费cpu资源,没有竞争到的线程会一直占用cpu资源进行cas操作
解决思路:让得不到锁的线程让出cpu
volatile int status=0;
void lock(){
while(!compareAndSet(0,1)){
yield();//自己实现,拿不到锁就释放cpu,等其他线程唤醒
}
//拿锁成功
}
void unlock(){
status=0;
}
要解决自旋锁的性能问题必须让竞争锁失败的线程不空转,而是在获取不到锁的时候把cpu资源让出来,yield方法能让出cpu资源,让线程竞争锁失败时,会调用yield方法让出cpu,自旋+yield的方式并没有完全解决问题,当系统只有两个线程竞争锁时,yield是由有效的,需要注意的是改方法只是当前让出cpu,有可能操作系统下次还是选择运行该线程,比如里面有2000个线程,想想会有什么问题?
volatile int status=0;
void lock(){
while(!compareAndSet(0,1)){
sleep(10);//时间设为固定,也很有问题
}
//拿锁成功
}
void unlock(){
status=0;
}
这正是AQS的核心思路,park也是挂起线程,释放cpu,等其他线程唤醒
volatile int status=0;
Queue parkQueue;//集合 数组 list
void lock(){
while(!compareAndSet(0,1)){
park();
}
//拿锁成功
...;
unlock();
}
void unlock(){
status=0;
}
void park(){
//将当前线程加入等待队列
parkQueue.add(currentThread);
//将当前线程是否cpu 阻塞
releaseCpu();
}
void lock_notify(){
//得到要唤醒的线程头部线程
Thread t = parkQueue.header();
//唤醒等待线程
unpark(t);
}
park的意思是停车,等待别人告诉可以开车再启动发动机unpark,一般是利用LockSupport类完成的,而他里面的park和unpart是native的
LockSupport.park();
LockSupport.unpark(Thread t);
所谓AQS,指的是AbstractQueuedSynchronizer,它提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架。是除了java自带的synchronized关键字之外的锁机制。这个类在java.util.concurrent.locks包。
AQS是Java并发包提供的一个同步基础机制,是并发包中实现Lock和其他同步机制(如:Semaphore、CountDownLatch和FutureTask等)的基础。具体用法是通过继承AQS实现其模板方法,然后将子类作为同步组件的内部类。
同步队列:AQS内部包含一个FIFO的同步等待队列,简单的说,没有成功获取控制权的线程会在这个队列中等待。
state:AQS内部管理了一个原子的int域作为内部状态信息,并提供了一些方法来访问该域,基于AQS实现的同步机制可以按自己的需要来灵活使用这个int域,比如:
AQS内部提供了一个ConditionObject类来支持独占模式下的(锁)条件,这个条件的功能与Object的wait和notify/notifyAll的功能类似,但更加明确和易用。
AQS一般的使用方式为定义一个实现AQS接口的非公有的内部帮助类作为内部代理,来实现具体同步机制的方法,如Lock的lock和unlock;AQS中也提供一些检测和监控内部队列和条件对象的方法,具体同步机制可以按需使用这些方法;AQS内部只有一个状态,即原子int域,如果基于AQS实现的类需要做序列化/反序列化,注意这一点。
AQS的核心思想是,
AQS的实现依赖内部的同步队列,也就是FIFO的双向队列,这个队列是用双线链表实现的
当出现锁竞争以及释放锁的时候,AQS同步队列中的节点会发生变化,首先看一下添加节点的场景。
这里会涉及到两个变化
head节点表示获取锁成功的节点,当头结点在释放同步状态时,会唤醒后继节点,如果后继节点获得锁成功,会把自己设置为头结点,节点的变化过程如下
这个过程也是涉及到两个变化
这里有一个小的变化,就是设置head节点不需要用CAS,原因是设置head节点是由获得锁的线程来完成的,而同步锁只能由一个线程获得,所以不需要CAS保证,只需要把head节点设置为原首节点的后继节点,并且断开原head节点的next引用即可
看一下Node结点类
static final class Node {
/** 表示节点在共享模式下等待的常量 */
static final Node SHARED = new Node();
/** 表示节点在独占模式下等待的常量 */
static final Node EXCLUSIVE = null;
/** 表示当前节点的线程被取消 */
static final int CANCELLED = 1;
/** 表示后继节点的线程需要被唤醒 */
static final int SIGNAL = -1;
/** 表示当前节点的线程正在等待某个条件 */
static final int CONDITION = -2;
/** 表示接下来的一个共享模式请求(acquireShared)要无条件的传递(往后继节点方向)下去 */
static final int PROPAGATE = -3;
/**
* 等待状态域, 取以下值:
* SIGNAL: 当前节点的后继节点已经(或即将)被阻塞(在等待),所以如果当前节点释放(控制权)
* 或者被取消时,必须唤醒其后继节点运行后继。为了避免竞争,请求方法必须首先
* 声明它们需要一个信号,然后(原子的)调用请求方法,如果失败,当前线程
* 进入阻塞状态。
* CANCELLED: 表示当前节点已经被取消(由于等待超时或被中断),从队列中移走。节点一旦进入被取消状态,就
* 不会再变成其他状态了。具体来说,一个被取消节点的线程永远不会再次被
* 阻塞
* CONDITION: 表示当前节点正处在一个条件队列中。当前节点直到转移时才会被作为一个
* 同步队列的节点使用。转移时状态域会被设置为0。(使用0值和其他定义值
* 并没有关系,只是为了简化操作)
* PROPAGATE: 表示一个共享的释放操作(releaseShared)应该被传递到其他节点。该状态
* 值在doReleaseShared过程中进行设置(仅在头节点),从而保证持续传递,
* 即使其他操作已经开始。 共享,表示状态要往后面传播
* 0: None of the above 初始状态
*
* 这些状态值之所以用数值来表示,目的是为了方便使用,非负的值意味着节点不需要信号(被唤醒)。
* 所以,一些代码中不需要针对特殊值去做检测,只需要检查符号(正负)即可。
*
* 针对普通的同步节点,这个域被初始化为0;针对条件(condition)节点,初始化为CONDITION(-2)
* 需要通过CAS操作来修改这个域(如果可能的话,可以使用volatile写操作)。
*/
volatile int waitStatus;
/**
* 指向当前节点的前驱节点,用于检测等待状态。这个域在入队时赋值,出队时置空。
* 而且,在取消前驱节点的过程中,可以缩短寻找非取消状态节点的过程。由于头节点
* 永远不会取消(一个节点只有请求成功才会变成头节点,一个被取消的节点永远不可
* 能请求成功,而且一个线程只能取消自己所在的节点),所以总是存在一个非取消状态节点。
*/
volatile Node prev;
/**
* 指向当前节点的后继节点,释放(控制权)时会唤醒该节点。这个域在入队时赋值,在跳过
* 取消状态节点时进行调整,在出队时置空。入队操作在完成之前并不会对一个前驱节点的
* next域赋值,所以一个节点的next域为null并不能说明这个节点在队列尾部。然而,如果
* next域为null,我们可以从尾节点通过前驱节点往前扫描来做双重检测。取消状态节点的
* next域指向自身,这样可以简化isOnSyncQueue的实现。
*/
volatile Node next;
/**
* 使当前节点入队的线程。在构造构造的时候初始化,使用后置为null。
*/
volatile Thread thread;
/**
* 指向下一个条件等待状态节点或者为特殊值(SHARED)。由于条件队列只有在独占模式下才
* 能访问,所以我们只需要一个普通的链表队列来保存处于等待状态的节点。它们在重新请
* 求的时候会转移到同步队列。由于条件只存在于独占模式下,所以如果是共享模式,就将
* 这域保存为一个特殊值(SHARED)。
*/
Node nextWaiter;
/**
* Returns true if node is waiting in shared mode
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
*
* @return the predecessor of this node
*/
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;
}
}
说明:节点类Node内部定义了一些常量,如节点模式、等待状态;Node内部有指向其前驱和后继节点的引用(类似双向链表);Node内部有保存当前线程的引用;Node内部的nextWaiter域在共享模式下指向一个常量SHARED,在独占模式下为null或者是一个普通的等待条件队列(只有独占模式下才存在等待条件)。
再看一下AQS中同步等待队列相关的域:
/**
* 同步等待队列的头节点,延迟初始化。除了初始化之外,只能通过setHead方法来改变
* 这个域。注:如果头结点存在,那么它的waitStatus可以保证一定不是CANCELLED。
*/
private transient volatile Node head;
/**
* 同步等待队列的尾节点,延迟初始化。只有通过enq方法添加一个新的等待节点的时候
* 才会改变这个域。
*/
private transient volatile Node tail;
AQS中有一个这样的属性定义,这个对于重入锁的实现来说,表示一个同步状态。它有两个含义的表示
private volatile int state;
需要注意的是:不同的AQS实现,state所表达的含义是不一样的。
/**
* The synchronization state.
*/
private volatile int state;
/**
* Returns the current value of synchronization state.
* This operation has memory semantics of a volatile read.
* @return current state value
*/
protected final int getState() {
return state;
}
/**
* Sets the value of synchronization state.
* This operation has memory semantics of a volatile write.
* @param newState the new state value
*/
protected final void setState(int newState) {
state = newState;
}
/**
* Atomically sets synchronization state to the given updated
* value if the current state value equals the expected value.
* This operation has memory semantics of a volatile read
* and write.
*
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that the actual
* value was not equal to the expected value.
*/
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
上面已经看到AQS内部的整体数据结构,一个同步等待队列+一个(原子的)int域。下面来从请求和释放两条主线来进行相关代码分析。
先分析独占模式,下面是我们常用的lock()方法
// ReentrantLock.lock()
public void lock(){
sync.lock();
}
sync是什么?
sync是一个静态内部类,它继承了AQS这个抽象类,前面说过AQS是一个同步工具,主要用来实现同步控制。我们在利用这个工具的时候,会继承它来实现同步控制功能。
通过进一步分析,发现Sync这个类有两个具体的实现,分别是 NofairSync(非公平锁)
, FailSync(公平锁)
。下面是类之间的关系
public class ReentrantLock implements Lock, java.io.Serializable
//下面3个都是ReentrantLock的内部类
// Sync继承了AQS
abstract static class Sync extends AbstractQueuedSynchronizer
//下面两个继承了Sync,简介继承了AQS
static final class NonfairSync extends Sync
static final class FairSync extends Sync
重新看lock方法
// ReentrantLock.lock()
public void lock(){
sync.lock();// sync为构造ReentrantLock实例时根据传入的bool后new出来NonfairSync实例或FairSync实例
}
// 内部类
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
// 通过cas修改state状态,表示争抢锁的操作。只要cas加锁成功,那么就执行,不去排队,抢了别人的所以叫非公平锁
if (compareAndSetState(0, 1))
// 争抢到了state,设置排他锁
setExclusiveOwnerThread(Thread.currentThread());
else
// 抢占锁失败,即state不为0。调用acquire来走锁竞争逻辑 // 这个1指的是如果可以重入的话就+1
acquire(1);
}
/*说明:
tryAcquire在AQS为抛异常的方法,所以子类要用必须重写
tryAcquire的意义就是独占模式中尝试获取锁
在独占模式下尝试请求(控制权)。这个方法(实现)应该查看一下对象的状态是否允许在独占模式下请求,如果允许再进行请求。
*
* 这个方法总是被请求线程执行,如果方法执行失败,会将当前线程放到
* 同步等待队列中(如果当前线程还不在同步等待队列中),直到被其他线程的释放
* 操作唤醒。可以用来实现Lock的tryLock方法。
*/
// 独占模式中尝试获取锁,如果成功就返回true,失败返回false // 它是重写AQS类中的tryAcquire方法,并且大家仔细看一下AQS中tryAcquire方法的定义,并没有实现,而是抛出异常。按照一般的思维模式,既然是一个不实现的模版方法,那应该定义成abstract,让子类来实现呀?大家想想为什么
protected final boolean tryAcquire(int acquires) {
// 非公平锁的尝试获取锁 // 父类Sync内的方法
return nonfairTryAcquire(acquires);
}
}
//-----------顺便对比一下公平锁和非公平锁-----------
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
- 第一步判断锁是不是自由状态,如果是则判断直接是否需要排队(hasQueuedPredecessors方法判断队列是否被初始化(如果没有初始化显然不需要排队)和是否需要排队(队列如果被初始化了,则自己有可能需要排队));如果hasQueuedPredecessors返回false,由于取反了故而不需要排队则进行CAS操作去上锁,如果需要排队则不会进入if分支当中,也不会进else if,会直接返回false表示加锁失败。
- 第二步如果不是自由状态则判断是不是重入,判断持有锁的线程是不是当前线程,如果不是则直接返回false加锁失败,如果是重入则把计数器+1。也说明了reentrantLock是可重入锁。
*/
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;
}
}
//如果C!=0,而且当前线程不等于拥有锁的线程则不会进else if 直接返回false,加锁失败
//如果C!=0,但是当前线程等于拥有锁的线程则表示这是一次重入,那么直接把状态+1表示重入次数+1
//那么这里也侧面说明了reentrantlock是可以重入的,因为如果是重入也返回true,也能lock成功
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
非公平锁和公平锁的区别再哪里呢?下图说明了公平和非公平的区别,记住一朝排队,永远排队。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
// 抽象方法
abstract void lock();
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
int c = getState();//获取state的值
if (c == 0) {//如果state为0代表无锁状态
if (compareAndSetState(0, acquires)) {
// 用cas拿锁成功,设置为独占线程
setExclusiveOwnerThread(current);
return true;
}
}
// 状态值不为0,但是拿锁的线程是当前线程,那么就重入
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;
}
protected final boolean tryRelease(int releases) {
// state-1
int c = getState() - releases;
// 当前线程并不是拿锁的线程,抛出错误
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 标记锁是否空闲
boolean free = false;
// state为0了,锁空闲了
if (c == 0) {
// 锁空闲,是否成功
free = true;
// 排他线程设置为null
setExclusiveOwnerThread(null);
}
// 更新state值
setState(c);
return free;
}
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
// 当前线程和拥有锁的线程是否为同一线程,即不是的话就排他
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 尝试获取独占锁,如果成功tryAcquire返回true,失败返回false后继续调用acquireQueued // tryAcquire是实现类重写的方法,实现类必须重写tryAcquire
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//已经没有获取到锁了//addWaiter:获取锁失败,把当前线程封装为Node,添加到AQS队列。将Node作为参数,通过自旋去尝试获取锁。// Node.EXCLUSIVE为null
// 将当前线程封装成Node添加到AQS队列尾部
selfInterrupt();
}
private Node addWaiter(Node mode) {
// 把当前线程封装为Node
Node node = new Node(Thread.currentThread(), mode);//mode为null
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
// 队尾不为空,说明队列中有结点在排队了,所以就当前线程去末尾排队
if (pred != null) {
// 当前结点的前向指针为上一个尾结点
node.prev = pred;
// cas替换尾结点
if (compareAndSetTail(pred, node)) { // 期望替换的是pred这个尾结点,但如果他被其他线程替换了之后,当前线程就替换不成功了
// 上一个尾结点的后继结点为当前结点
pred.next = node;
return node;
}
}
// 尾结点为空,或者原来队列有元素但在替换尾结点的过程中出错,被其他线程把尾结点替换了 // 第一个线程进来的时候一定走这,初始化队列,直接把当前结点设置为头结点,表示正在运行的线程
enq(node);
return node;
}
// 前面说了两种情况会来这:队列为空 或 队列有值但是cas替换尾结点时替换失败返回false
private Node enq(final Node node) { //enqueue入队
for (;;) {
// 获取尾结点(最新的)
Node t = tail;
// 如果尾结点为空,代表队列空,即代表队列没人使用过还没初始化。
if (t == null) { // 进入这只用作初始化队列使用,全程只会进入一次,后面tail总是有值,head==tail即代表对列为空
// 设置头结点的过程中如果返回false代表被其他线程创建头结点了
if (compareAndSetHead(new Node())) //为什么随便new了个Node?因为走到这说明已经有个线程在执行了获取了锁,我们创建个队列把当前线程放入队列。而正常来说我们要把头结点当做正在执行的线程,但没有办法了,我们第一个获取到锁的线程并没有给我们留下信息,那我们干脆随便new个头结点让他代表正在执行的线程就行了,我们方面把当前线程添加到他后面就行
tail = head;//设置为头结点,而且把尾结点也设置了,别的接地那就能放在后面了
} else {
// 队列被初始化过了// 到这的原因是原来要添加尾结点的,但是cas时原来尾结点被其他线程更改了,cas没成功,这里重新cas
// 获取原来尾结点,然后当前线程重新替换该尾结点 // 其实就相当于addWaiter里循环cas
node.prev = t;
if (compareAndSetTail(t, node)) {//如果又失败了怎么办?反正外卖有for,失败了重新来就行,知道放到队尾
t.next = node;
return t;
}
}
}
// 队头里的thread永远为空
}
// 尝试获取独占锁失败,当前线程添加到尾结点后,循环询问下一个是不是该我了,
final boolean acquireQueued(final Node node, int arg) {//参数为尾结点 //arg一般为1
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 获取尾结点的前驱结点 // 下次循环可能还进来,但前驱结点相对头结点的位置是变化的,一旦前驱结点的称为头结点,当前结点就应该去准备获取锁了
final Node p = node.predecessor();
// 如果尾结点为队里中除头结点外唯一结点(只有前驱为head才有资格进行锁的争夺) // 如果获取锁失败又重新插入到尾结点
// 如果下一个该我了再看前一个执行完没有
if (p == head && tryAcquire(arg)) {
// 获取了锁,将当前结点设置为头结点,表示我开始运行了。头结点是获得了锁的结点
setHead(node);
// 原来头结点的后继结点为null,帮助GC
p.next = null; // help GC
// 没有失败,获取锁成功
failed = false;
// 传递中断状态,并返回
return interrupted;
}
// 前驱结点还没有执行/执行完,不用急,还轮不到我,那我可以先挂起让出cpu
// 现在是获取失败了,那应不应该挂起呢 // 根据结点的waitStatus决定是否需要挂起线程
if (shouldParkAfterFailedAcquire(p, node) && // 传入了前驱结点和当前结点
parkAndCheckInterrupt()) // 如果应该没阻塞,那么就阻塞,并检测终端状态
// 若前面为true,则执行挂起,待下次唤醒的时候检测中断的标志
interrupted = true;
}
} finally { // 唤醒后才执行
if (failed)// 如果抛出异常则取消锁的获取,进行出队(sync queue)操作
cancelAcquire(node);
}
}
// 获取锁失败,查看当前线程是否要挂起(阻塞)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 参数为前驱结点和当前结点
// 获取前驱结点的等待状态waitStatus // 前面并没有改过,所以是0
int ws = pred.waitStatus;
// 如果前驱结点为SIGNAL代表当前结点已经声明了需要唤醒,那么就可以阻塞当前结点了,返回true
// 一个节点在其被阻塞之前需要线程"声明"一下其需要唤醒(就是将其前驱节点的等待状态设置为SIGNAL,注意其前驱节点不能是取消状态,如果是,要跳过)
// 如果前驱结点的waitStatus为-1 SIGNAL,说明前驱结点具有唤醒后继结点的功能,直接返回后接着进行挂起操作就可以,以后前驱结点执行的时候会判断到他需要唤醒别的结点,就把当前结点唤醒了
if (ws == Node.SIGNAL)//-1 // 当前线程需要被unpark唤醒 // 为什么不直接=0时候就park?因为为了让他多自旋一次。此外0时候会做一些事情
return true;
// 前驱结点 CANCELLED==1,说明前驱结点的线程被取消了,也就不会唤醒其他结点了,我们如果把他放到我们当前结点的前面,那么当前结点就没人唤醒了,所以我们要把前面被取消的结点过滤掉,连接到个有效的前驱结点,让当前结点能被前面的结点唤醒。
//从前驱节点开始逐步循环找到一个没有被“CANCELLED”节点设置为当前节点的前节点,返回false。在下次循环执行shouldParkAfterFailedAcquire时,返回true。这个操作实际是把队列中CANCELLED的节点剔除掉。 // 直接把他们丢弃了 他们进入不了线程了
if (ws > 0) { // 设置 “当前节点”的 “当前前继节点” 为 “‘原前继节点'的前继节点”。
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else { // CONDITION/PROPAGATE/0 如果前继节点为“0”或者“共享锁”状态,则设置前继节点为SIGNAL状态。
/*
前面的源码中,都没有看到有设置waitStatus的,所有每个新的node入队时,waitStatus都为0
正常情况下,前驱结点就是之前的tail,那么它的waitStatus应该是0
用CAS将前驱结点的waitStatus设置为NODE.SIGNAL,这样当前结点就能被他唤醒了
接下来方法会返回false,还会继续尝试一下请求,以确保在阻塞之前确实无法请求成功。
改的是上一个结点的
上一个结点本来是0,然后改成1
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
// 最后去执行阻塞的时候,当前线程的ws还是0,上上一个结点是-1了
}
// 方法返回false,再走一次之前函数里的for循环
// 然后再次来到此方法,然后进入第一个if返回true
// 为什么不自己改成-1而是让别人来改呢?他阻塞了不能改为-1了,我自己看不到自己睡觉
return false;
/*
接下来说说如果 shouldParkAfterFailedAcquire(p, node) 返回false的情况:仔细看shouldParkAfterFailedAcquire(p, node),我们可以发现,其实第一次进来的时候,一般都不会返回true的,原因很简单,
前驱节点的 waitStatus=-1 是依赖于后继节点设置的。
也就是说,我都还没给前驱设置-1呢,怎么可能是true呢,但是要看到,这个方法是套在循环里的,所以第二次进来的时候状态就是-1了。
*/
}
// 当前线程应该被挂起阻塞,去执行阻塞
private final boolean parkAndCheckInterrupt() {//如果shouldParkAfterFailedAcquire返回了true,则会执行: parkAndCheckInterrupt()方法,它是通过LockSupport.park(this)将当前线程挂起到WATING状态,它需要等待一个中断、unpark方法来唤醒它,通过这样一种FIFO的机制的等待,来实现了Lock的操作。
// 真正地负责阻塞当前线程 // 在这阻塞了
LockSupport.park(this);//LockSupport类是Java6引入的一个类,提供了基本的线程同步原语。LockSupport实际上是调用了Unsafe类里的函数,归结到Unsafe里,只有两个函数:park()和unpark()
//线程被唤醒,方法返回当前线程的中断状态,并重置当前线程的中断状态(置为false)。
return Thread.interrupted();
}
// acquireQueued最后finally块中的cancelAcquire方法。
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
//跳过首先将要取消的节点的thread域置空。
node.thread = null;
//跳过状态为"取消"的前驱节点。
Node pred = node.prev;
//node前面总是会存在一个非"取消"状态的节点,所以这里不需要null检测。
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext节点(node节点前面的第一个非取消状态节点的后继节点)是需要"断开"的节点。
// 下面的CAS操作会达到"断开"效果,但(CAS操作)也可能会失败,因为可能存在其他"cancel"
// 或者"singal"的竞争
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.waitStatus = Node.CANCELLED;
// 如果当前节点是尾节点,那么删除当前节点(将当前节点的前驱节点设置为尾节点)。
if (node == tail && compareAndSetTail(node, pred)) {
//将前驱节点(已经设置为尾节点)的next置空。
compareAndSetNext(pred, predNext, null);
} else {
//如果当前节点不是尾节点,说明后面有其他等待线程,需要做一些唤醒工作。
// 如果当前节点不是头节点,那么尝试将当前节点的前驱节点
// 的等待状态改成SIGNAL,并尝试将前驱节点的next引用指向
// 其后继节点。否则,唤醒后继节点。
int ws;
if (pred != head &&
( (ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL)) )
&& pred.thread != null) {
//如果当前节点的前驱节点不是头节点,那么需要给当前节点的后继节点一个"等待唤醒"的标记,
//即 将当前节点的前驱节点等待状态设置为SIGNAL,然后将其设置为当前节点的后继节点的前驱节点....(真绕!)
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
//否则,唤醒当前节点的后继节点。
unparkSuccessor(node);
}
//前面提到过,取消节点的next引用会指向自己。
node.next = node; // help GC
}
}
/** 如果node存在后继节点,唤醒后继节点。 */
private void unparkSuccessor(Node node) {
/*
* 如果node的等待状态为负数(比如:可能需要一个信号),尝试去清空
* "等待唤醒"的状态(将状态置为0),即使设置失败,或者该状态已经
* 被正在等待的线程修改,也没有任何影响。
*/
int ws = node.waitStatus;
if (ws < 0) //如果当前节点的状态小于0,尝试设置为0。
compareAndSetWaitStatus(node, ws, 0);
/*
* 需要唤醒的线程在node的后继节点,一般来说就是node的next引用指向的节点。
* 但如果next指向的节点被取消或者为null,那么就同步等待队列的队尾反向查找离
* 当前节点最近的且状态不是"取消"的节点。
*/
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);
}
/*
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;
}
}
//如果C!=0,而且当前线程不等于拥有锁的线程则不会进else if 直接返回false,加锁失败
//如果C!=0,但是当前线程等于拥有锁的线程则表示这是一次重入,那么直接把状态+1表示重入次数+1
//那么这里也侧面说明了reentrantlock是可以重入的,因为如果是重入也返回true,也能lock成功
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
*/
// tryAcquire里 锁空闲 判断自己是否需要排队
public final boolean hasQueuedPredecessors() {
// 如果没有队列的时候,h和t都是null,返回false,说明他不需要排队,用cas加锁
// 如果队列被初始化的时候,如果队列中元素>1,队列中元素==1,
// >1的时候,第一个h != t满足,头结点的后继结点不为空,当前线程不是头结点的后继结点 ,不满足
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
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()); // 当前线程去询问要不要去排队//是不是第一个排队的人来询问需不需要排队
}
// 完成阻塞 //阻塞恢复时也是从这里接着执行
final boolean acquireQueued(final Node node, int arg) {//这里的node 就是当前线程封装的那个node 下文叫做nc
boolean failed = true;
try {
//同样是一个标志
boolean interrupted = false;
//死循环
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
// 排队的阻塞t2线程被唤醒后执行这里,把当前结点设为新的头部 // 还会把当前结点里的thread置为空(用不到了。他只用作唤醒,唤醒后就没用了,方便以后gc),还会把当前结点指向前驱结点的指针去掉
setHead(node); /*private void setHead(Node node) { head = node; node.thread = null; node.prev = null; }*/
// 把前驱结点指向当前结点的指针去掉。放GC把前面的结点回收掉。至于当前结点指向前驱结点的指针在上一步setHead里去掉了
p.next = null; // help GC
//设置表示---记住记加锁成功的时候为false
failed = false;
//返回false;为什么返回false 为了不调用50行---acquire方法当中的selfInterrupt方法;为什么不调用?下次解释比较复杂
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
//改上一个节点的状态成功之后;自己park;到此加锁过程说完了
parkAndCheckInterrupt())//阻塞了在这不动了//被前面结点(头结点)指向完唤醒后接着去自旋
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
}
LockSupportLockSupport类是Java6引入的一个类,提供了基本的线程同步原语。LockSupport实际上是调用了Unsafe类里的函数,归结到Unsafe里,只有两个函数:
public native void unpark(Thread jthread);
public native void park(boolean isAbsolute, long time);
unpark函数为线程提供“许可(permit)”,线程调用park函数则等待“许可”。这个有点像信号量,但是这个“许可”是不能叠加的,“许可”是一次性的。 permit相当于0/1的开关,默认是0,调用一次unpark就加1变成了1.调用一次park会消费permit,又会变成0。 如果再调用一次park会阻塞,因为permit已经是0了。直到permit变成1.这时调用unpark会把permit设置为1.每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark不会累积
// ReentrantLock.java
public void unlock() {
sync.release(1);
}
//AbstractQueuedSynchronizer.java
public final boolean release(int arg) {
if (tryRelease(arg)) {
// 进入这里的时候代表锁已经为空了
Node h = head;
if (h != null && h.waitStatus != 0)
// 把头结点的后继结点unpark。常规流程是进入后unpark那个后继结点的线程,而且unpart后,接下来执行的其实是我们之前获取锁时候阻塞的地方,从阻塞的地方接着去自旋获取锁,回到acQuireQueued()
unparkSuccessor(h);//unpark后继者
return true;
}
return false;
}
// ReentrantLock
protected final boolean tryRelease(int releases) {
// state-1
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
// 如果释放的线程和获取锁的线程不是同一个,抛出非法监视器状态异常
throw new IllegalMonitorStateException();
boolean free = false;
// 如果释放后锁为空了,就清空state后,返回true
if (c == 0) { //在排它锁中,加锁的时候状态会增加1(当然可以自己修改这个值),在解锁的时候减掉1,同一个锁,在可以重入后,可能会被叠加为2、3、4这些值,只有unlock()的次数与lock()的次数对应才会将Owner线程设置为空,而且也只有这种情况下才会返回true。
// 由于重入的关系,不是每次释放锁c都等于0,
// 直到最后一次释放锁时,才会把当前线程释放
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
// AbstractQueuedSynchronizer.java
private void unparkSuccessor(Node node) {//继任者 // 参数为head
int ws = node.waitStatus;
// 如果头结点的waitStatus为负的,把头结点waitStatus改为0
if (ws < 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 s = node.next;//头结点的后继结点
// 如果没有后继结点 或 头结点的waitStatus为整数CANCEL(被取消了)
if (s == null || s.waitStatus > 0) {
s = null;
// 从尾结点向前遍历,拿到从前往后的第一个能被唤醒的结点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
//至于为什么从尾部开始向前遍历,因为在doAcquireInterruptibly.cancelAcquire方法的处理过程中只设置了next的变化,没有设置prev的变化,在最后有这样一行代码:node.next = node,如果这时执行了unparkSuccessor方法,并且向后遍历的话,就成了死循环了,所以这时只有prev是稳定的
s = t;
}
//内部首先会发生的动作是获取head节点的next节点,如果获取到的节点不为空,则直接通过:“LockSupport.unpark()”方法来释放对应的被挂起的线程,这样一来将会有一个节点唤醒后继续进入循环进一步尝试tryAcquire()方法来获取锁
if (s != null)
LockSupport.unpark(s.thread);//释放后继结点,让他去获取cpu
}
上面非公平锁分析完了,但哪里不公平了?
答案是线程刚进来要获取锁的时候,如果直接cas就是非公平了。如果先去排队尾再cas就公平
同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用):
自定义同步器在实现的时候只需要实现共享资源state的获取和释放方式即可,至于具体线程等待队列的维护,AQS已经在顶层实现好了。自定义同步器实现的时候主要实现下面几种方法:
// 独占式获取锁
acquire
acquireInterruptibly
tryAcquireNanos
release
// 共享式
acquireShare
acquireShareInterruptibly
tryAcquireShareNanos
releaseShared
// 同步状态
state:volatile
getState
setState
compareAndSetState
//需要子类覆盖的流程方法
独占式获取
tryAcquire
独占式释放
tryRelease
共享式获取
tryAcquireShared
共享式释放
tryReleaseShared
// 同步器是否处于独占模式
isHeldExclusively
此外,使用
getState
,setState
,compareAndSetState
这几个方法来设置获取状态
ReentrantLock为例,(可重入独占式锁):
state初始化为0,表示未锁定状态,A线程lock()时,会调用tryAcquire()独占锁并将state+1.之后其他线程再想tryAcquire的时候就会失败,直到A线程unlock()到state=0为止,其他线程才有机会获取该锁。A释放锁之前,自己也是可以重复获取此锁(state累加),这就是可重入的概念。
注意:获取多少次锁就要释放多少次锁,保证state是能回到零态的。
以CountDownLatch为例,
任务分N个子线程去执行,state就初始化 为N,N个线程并行执行,每个线程执行完之后countDown()一次,state就会CAS减一。当N子线程全部执行完毕,state=0,会unpark()主调用线程,主调用线程就会从await()函数返回,继续之后的动作。
一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。
在acquire() acquireShared()两种方式下,线程在等待队列中都是忽略中断的,acquireInterruptibly()/acquireSharedInterruptibly()是支持响应中断的。
AQS的1、队列没有初始化,则不需要排队,直接去加锁,但是可能会失败;为什么会失败呢? 假设两个线程同时来lock,都看到队列没有初始化,都认为不需要排队,都去进行CAS修改计数器;但是肯定有一个会失败,这个时候他就会初始化队列并排队。
2、队列被初始化了,但是当前线程过来加锁,发觉队列当中头结点h就是自己,比如重入,因此不需要排队。
h != t 判断首不等于尾这里要分三种情况
1、队列没有初始化,也就是第一个线程来加锁,h和t都是null,&&运算所以后面不执行,直接返回false,但是这个方法取反了,所以会直接去cas加锁。
第一种情况总结:队列没有初始化,没人排队,那么我也不需要排队,直接上锁,直接去看能不能办理业务。
2、队列被初始化了,后面我们会分析队列初始化的流程,如果队列被初始化那么h!=t则成立;h != t 返回true;但是是&&运算,故而还需要进行后续的判断 ,(有人可能会疑问,比如队列里面只有一个数据,那么头和尾都是同一个怎么会成立呢?其实这是第三种情况–队列里面只有一个数据;这里先不考虑,假设现在队列里面有大于1个数据),继续判断把h.next赋值给s;s有是头的下一个,则表示他是队列当中参与排队的线程而且是排在最前面的;为什么是s最前面不是h嘛?诚然h是队列里面的第一个,但是不是排队的第一个;因为h是持有锁的,但是不参与排队;这个也很好理解,比如你去买火车票,你如果是第一个这个时候售票员已经在给你服务了,你不算排队,你后面的才算排队;然后判断s是否等于空,其实就是判断队列里面是否只有一个数据;假设队列大于1个,那么肯定不成立(s==null---->false),因为大于一个h.next肯定不为空;由于是||运算如果返回false,还要判断s.thread != Thread.currentThread();这里又分为两种情况:
2.1 s.thread != Thread.currentThread() 返回true,就是当前线程不等于在排队的第一个线程s;那么这个时候整体结果就是h!=t:true; (s==null false || s.thread != Thread.currentThread() true------> 最后true)结果: true && true 方法最终放回true,那么去则需要去排队,其实这样符合情理,队列不为空,有人在排队,而且第一个排队的人和现在来参与竞争的人不是同一个,那么你就乖乖去排队。
2.2 s.thread != Thread.currentThread() 返回false 表示当前来参与竞争锁的线程和第一个排队的线程是同一个线程 * 那么这个时候整体结果就是h!=t:true; (s==null false || s.thread != Thread.currentThread() false------> 最后false)结果 true && false 方法最终放回false,那么去则不需要去排队 * 不需要排队则调用 compareAndSetState(0, acquires) 去改变计数器尝试上锁;这里又分为两种情况:
<2.2.1>、第一种情况加锁成功?有人会问为什么会成功啊,很简单假如这个时候h也就是持有锁的那个线程执行完了,释放锁了,那么肯定成功啊;成功则执行 setExclusiveOwnerThread(current); 然后返回true 。
<2.2.2> 、第二种情况加锁失败?有人会问为什么会失败啊。很简单假如这个时候h也就是持有锁的那个线程没执行完,没释放锁,那么肯定失败啊;失败则直接返回false,不会进else if,但是他会去看看那个第一个排队的人是不是自己,如果是自己那么他就去尝试加锁;尝试看看锁有没有释放
第二种情况总结,如果队列被初始化了,而且至少有一个人在排队那么自己也去排队;但是他会去看看那个第一个排队的人是不是自己,如果是自己那么他就去尝试加锁;尝试看看锁有没有释放。
3、队列被初始化了,但是里面只有一个数据;什么情况下才会出现这种情况呢?可能有人会说ts加锁的时候里面就只有一个数据;其实不是,因为队列初始化的时候会虚拟一个h作为头结点,当前线程作为第一个排队的节点, 为什么这么做呢?因为aqs认为h永远是不排队的,假设你不虚拟节点出来那么ts就是h,而ts其实需要排队的,因为这个时候tf可能没有执行完,ts得不到锁,故而他需要排队;,那么为什么要虚拟为什么ts不直接排在tf之后呢,上面已经时说明白了,tf来上锁的时候队列都没有,他不进队列,故而ts无法排在tf之后,只能虚拟一个null节点出来;那么问题来了,究竟什么时候才会出现队列当中只有一个数据呢?假设原先队列里面有5个人在排队,当前面4个都执行完了,轮到第五个线程得到锁的时候;他会把自己设置成为头部,而尾部又没有,故而队列当中只有一个h就是第五个 * 至于为什么需要把自己设置成头部;其实已经解释了,因为这个时候五个线程已经不排队了,他拿到锁了,所以他不参与排队,故而需要设置成为h;即头部;所以这个时间内,队列当中只有一个节点 * 关于加锁成功后把自己设置成为头部的源码,后面会解析到;继续第三种情况的代码分析,记得这个时候队列已经初始化了,但是只有一个数据,并且这个数据所代表的线程是持有锁 * h != t false 由于后面是&&运算,故而返回false可以不参与运算,整个方法返回false;不需要排队
第三种情况总结:如果队列当中只有一个节点,而这种情况我们分析了,这个节点就是当前持有锁的那个节点,故而我不需要排队,进行cas。
如果是第一个线程tf,那么和队列无关,线程直接持有锁。并且也不会初始化队列,如果接下来的线程都是交替执行,那么永远和AQS队列无关,都是直接线程持有锁,如果发生了竞争,比如tf持有锁的过程中T2来lock,那么这个时候就会初始化AQS,初始化AQS的时候会在队列的头部虚拟一个Thread为NULL的Node,因为队列当中的head永远是持有锁的那个node(除了第一次会虚拟一个,其他时候都是持有锁的那个线程锁封装的node),现在第一次的时候持有锁的是tf而tf不在队列当中所以虚拟了一个node节点,队列当中的除了head之外的所有的node都在park,当tf释放锁之后unpark某个(基本是队列当中的第二个,为什么是第二个呢?前面说过head永远是持有锁的那个node,当有时候也不会是第二个,比如第二个被cancel之后,至于为什么会被cancel,不在我们讨论范围之内,cancel的条件很苛刻,基本不会发生)node之后,node被唤醒,假设node是t2,那么这个时候会首先把t2变成head(sethead),在sethead方法里面会把t2代表的node设置为head,并且把node的Thread设置为null,为什么需要设置null?其实原因很简单,现在t2已经拿到锁了,node就不要排队了,那么node对Thread的引用就没有意义了。所以队列的head里面的Thread永远为null。
synchronized改的是this(的对象头),而lock改的是state
简单应用
Mutex:不可重入互斥锁,锁资源(state)只有两种状态:0:未被锁定;1:锁定。
class Mutex implements Lock, java.io.Serializable {
// 自定义同步器
private static class Sync extends AbstractQueuedSynchronizer {
// 判断是否锁定状态
protected boolean isHeldExclusively() {
return getState() == 1;
}
// 尝试获取资源,立即返回。成功则返回true,否则false。
public boolean tryAcquire(int acquires) {
assert acquires == 1; // 这里限定只能为1个量
if (compareAndSetState(0, 1)) {//state为0才设置为1,不可重入!
setExclusiveOwnerThread(Thread.currentThread());//设置为当前线程独占资源
return true;
}
return false;
}
// 尝试释放资源,立即返回。成功则为true,否则false。
protected boolean tryRelease(int releases) {
assert releases == 1; // 限定为1个量
if (getState() == 0)//既然来释放,那肯定就是已占有状态了。只是为了保险,多层判断!
throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);//释放资源,放弃占有状态
return true;
}
}
// 真正同步类的实现都依赖继承于AQS的自定义同步器!
private final Sync sync = new Sync();
//lock<-->acquire。两者语义一样:获取资源,即便等待,直到成功才返回。
public void lock() {
sync.acquire(1);
}
//tryLock<-->tryAcquire。两者语义一样:尝试获取资源,要求立即返回。成功则为true,失败则为false。
public boolean tryLock() {
return sync.tryAcquire(1);
}
//unlock<-->release。两者语文一样:释放资源。
public void unlock() {
sync.release(1);
}
//锁是否占有状态
public boolean isLocked() {
return sync.isHeldExclusively();
}
}
同步类在实现时一般都将自定义同步器(sync)定义为内部类,供自己使用;而同步类自己(Mutex)则实现某个接口,对外服务。
每个Condition包含一个等待队列,他是一个单向链表,但也维护这firstWaiter、lastWaiter
如果一个锁有两个condition,那么它就有两个队列
同步器维护这正常的双向链表,还维护这若干个conditoin单向队列
如果调用了await方法,就会把当前结点从同步队列里移动(重新包装)到condition等待队列中
如果调用了signal方法,就会加入到同步队列的尾部。
作用
park开头的方法;
unpark(Thread thread)方法
package java.util.concurrent.locks;
import sun.misc.Unsafe;
/**
* Basic thread blocking primitives for creating locks and other
* synchronization classes.
*
* This class associates, with each thread that uses it, a permit
* (in the sense of the {@link java.util.concurrent.Semaphore
* Semaphore} class). A call to {@code park} will return immediately
* if the permit is available, consuming it in the process; otherwise
* it may block. A call to {@code unpark} makes the permit
* available, if it was not already available. (Unlike with Semaphores
* though, permits do not accumulate. There is at most one.)
*
*
Methods {@code park} and {@code unpark} provide efficient
* means of blocking and unblocking threads that do not encounter the
* problems that cause the deprecated methods {@code Thread.suspend}
* and {@code Thread.resume} to be unusable for such purposes: Races
* between one thread invoking {@code park} and another thread trying
* to {@code unpark} it will preserve liveness, due to the
* permit. Additionally, {@code park} will return if the caller's
* thread was interrupted, and timeout versions are supported. The
* {@code park} method may also return at any other time, for "no
* reason", so in general must be invoked within a loop that rechecks
* conditions upon return. In this sense {@code park} serves as an
* optimization of a "busy wait" that does not waste as much time
* spinning, but must be paired with an {@code unpark} to be
* effective.
*
*
The three forms of {@code park} each also support a
* {@code blocker} object parameter. This object is recorded while
* the thread is blocked to permit monitoring and diagnostic tools to
* identify the reasons that threads are blocked. (Such tools may
* access blockers using method {@link #getBlocker(Thread)}.)
* The use of these forms rather than the original forms without this
* parameter is strongly encouraged. The normal argument to supply as
* a {@code blocker} within a lock implementation is {@code this}.
*
*
These methods are designed to be used as tools for creating
* higher-level synchronization utilities, and are not in themselves
* useful for most concurrency control applications. The {@code park}
* method is designed for use only in constructions of the form:
*
*
{@code
* while (!canProceed()) { ... LockSupport.park(this); }}
*
* where neither {@code canProceed} nor any other actions prior to the
* call to {@code park} entail locking or blocking. Because only one
* permit is associated with each thread, any intermediary uses of
* {@code park} could interfere with its intended effects.
*
* Sample Usage. Here is a sketch of a first-in-first-out
* non-reentrant lock class:
*
{@code
* class FIFOMutex {
* private final AtomicBoolean locked = new AtomicBoolean(false);
* private final Queue waiters
* = new ConcurrentLinkedQueue();
*
* public void lock() {
* boolean wasInterrupted = false;
* Thread current = Thread.currentThread();
* waiters.add(current);
*
* // Block while not first in queue or cannot acquire lock
* while (waiters.peek() != current ||
* !locked.compareAndSet(false, true)) {
* LockSupport.park(this);
* if (Thread.interrupted()) // ignore interrupts while waiting
* wasInterrupted = true;
* }
*
* waiters.remove();
* if (wasInterrupted) // reassert interrupt status on exit
* current.interrupt();
* }
*
* public void unlock() {
* locked.set(false);
* LockSupport.unpark(waiters.peek());
* }
* }}
*/
public class LockSupport {
private LockSupport() {} // Cannot be instantiated.
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
/**
* Makes available the permit for the given thread, if it
* was not already available. If the thread was blocked on
* {@code park} then it will unblock. Otherwise, its next call
* to {@code park} is guaranteed not to block. This operation
* is not guaranteed to have any effect at all if the given
* thread has not been started.
*
* @param thread the thread to unpark, or {@code null}, in which case
* this operation has no effect
*/
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
/**
* Disables the current thread for thread scheduling purposes unless the
* permit is available.
*
* If the permit is available then it is consumed and the call returns
* immediately; otherwise
* the current thread becomes disabled for thread scheduling
* purposes and lies dormant until one of three things happens:
*
*
* - Some other thread invokes {@link #unpark unpark} with the
* current thread as the target; or
*
*
- Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread; or
*
*
- The call spuriously (that is, for no reason) returns.
*
*
* This method does not report which of these caused the
* method to return. Callers should re-check the conditions which caused
* the thread to park in the first place. Callers may also determine,
* for example, the interrupt status of the thread upon return.
*
* @param blocker the synchronization object responsible for this
* thread parking
*/
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
/**
* Disables the current thread for thread scheduling purposes, for up to
* the specified waiting time, unless the permit is available.
*
* If the permit is available then it is consumed and the call
* returns immediately; otherwise the current thread becomes disabled
* for thread scheduling purposes and lies dormant until one of four
* things happens:
*
*
* - Some other thread invokes {@link #unpark unpark} with the
* current thread as the target; or
*
*
- Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread; or
*
*
- The specified waiting time elapses; or
*
*
- The call spuriously (that is, for no reason) returns.
*
*
* This method does not report which of these caused the
* method to return. Callers should re-check the conditions which caused
* the thread to park in the first place. Callers may also determine,
* for example, the interrupt status of the thread, or the elapsed time
* upon return.
*
* @param blocker the synchronization object responsible for this
* thread parking
* @param nanos the maximum number of nanoseconds to wait
* @since 1.6
*/
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, nanos);
setBlocker(t, null);
}
}
/**
* Disables the current thread for thread scheduling purposes, until
* the specified deadline, unless the permit is available.
*
* If the permit is available then it is consumed and the call
* returns immediately; otherwise the current thread becomes disabled
* for thread scheduling purposes and lies dormant until one of four
* things happens:
*
*
* - Some other thread invokes {@link #unpark unpark} with the
* current thread as the target; or
*
*
- Some other thread {@linkplain Thread#interrupt interrupts} the
* current thread; or
*
*
- The specified deadline passes; or
*
*
- The call spuriously (that is, for no reason) returns.
*
*
* This method does not report which of these caused the
* method to return. Callers should re-check the conditions which caused
* the thread to park in the first place. Callers may also determine,
* for example, the interrupt status of the thread, or the current time
* upon return.
*
* @param blocker the synchronization object responsible for this
* thread parking
* @param deadline the absolute time, in milliseconds from the Epoch,
* to wait until
* @since 1.6
*/
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(true, deadline);
setBlocker(t, null);
}
/**
* Returns the blocker object supplied to the most recent
* invocation of a park method that has not yet unblocked, or null
* if not blocked. The value returned is just a momentary
* snapshot -- the thread may have since unblocked or blocked on a
* different blocker object.
*
* @param t the thread
* @return the blocker
* @throws NullPointerException if argument is null
* @since 1.6
*/
public static Object getBlocker(Thread t) {
if (t == null)
throw new NullPointerException();
return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
}
/**
* Disables the current thread for thread scheduling purposes unless the
* permit is available.
*
* If the permit is available then it is consumed and the call
* returns immediately; otherwise the current thread becomes disabled
* for thread scheduling purposes and lies dormant until one of three
* things happens:
*
*
*
* - Some other thread invokes {@link #unpark unpark} with the
* current thread as the target; or
*
*
- Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread; or
*
*
- The call spuriously (that is, for no reason) returns.
*
*
* This method does not report which of these caused the
* method to return. Callers should re-check the conditions which caused
* the thread to park in the first place. Callers may also determine,
* for example, the interrupt status of the thread upon return.
*/
public static void park() {
UNSAFE.park(false, 0L);
}
/**
* Disables the current thread for thread scheduling purposes, for up to
* the specified waiting time, unless the permit is available.
*
* If the permit is available then it is consumed and the call
* returns immediately; otherwise the current thread becomes disabled
* for thread scheduling purposes and lies dormant until one of four
* things happens:
*
*
* - Some other thread invokes {@link #unpark unpark} with the
* current thread as the target; or
*
*
- Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread; or
*
*
- The specified waiting time elapses; or
*
*
- The call spuriously (that is, for no reason) returns.
*
*
* This method does not report which of these caused the
* method to return. Callers should re-check the conditions which caused
* the thread to park in the first place. Callers may also determine,
* for example, the interrupt status of the thread, or the elapsed time
* upon return.
*
* @param nanos the maximum number of nanoseconds to wait
*/
public static void parkNanos(long nanos) {
if (nanos > 0)
UNSAFE.park(false, nanos);
}
/**
* Disables the current thread for thread scheduling purposes, until
* the specified deadline, unless the permit is available.
*
* If the permit is available then it is consumed and the call
* returns immediately; otherwise the current thread becomes disabled
* for thread scheduling purposes and lies dormant until one of four
* things happens:
*
*
* - Some other thread invokes {@link #unpark unpark} with the
* current thread as the target; or
*
*
- Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread; or
*
*
- The specified deadline passes; or
*
*
- The call spuriously (that is, for no reason) returns.
*
*
* This method does not report which of these caused the
* method to return. Callers should re-check the conditions which caused
* the thread to park in the first place. Callers may also determine,
* for example, the interrupt status of the thread, or the current time
* upon return.
*
* @param deadline the absolute time, in milliseconds from the Epoch,
* to wait until
*/
public static void parkUntil(long deadline) {
UNSAFE.park(true, deadline);
}
/**
* Returns the pseudo-randomly initialized or updated secondary seed.
* Copied from ThreadLocalRandom due to package access restrictions.
*/
static final int nextSecondarySeed() {
int r;
Thread t = Thread.currentThread();
if ((r = UNSAFE.getInt(t, SECONDARY)) != 0) {
r ^= r << 13; // xorshift
r ^= r >>> 17;
r ^= r << 5;
}
else if ((r = java.util.concurrent.ThreadLocalRandom.current().nextInt()) == 0)
r = 1; // avoid zero
UNSAFE.putInt(t, SECONDARY, r);
return r;
}
// Hotspot implementation via intrinsics API
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); }
}
}
交替执行的话是不会产生队列的,即lock unlock lock unlock
https://mp.weixin.qq.com/s/-MXuwOEaupFyh_2yylEZoA
https://blog.csdn.net/zhousenshan/article/details/77815022
https://blog.csdn.net/TJtulong/article/details/105345940
视频:https://www.bilibili.com/video/BV19J411Q7R5
视频文档:https://blog.csdn.net/hskw444273663/article/details/103018276