java多线程【4】AQS

Atomic

高并发的情况下,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
    }
}

AtomicInteger源码

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(); } }

getAndAddInt

先去内存拿最新的地址,然后不断进行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;
}

其他Atomic

AtomicReference

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问题有两个可用的类

  • AtomicMarkableReference: 用boolean标记有没有人动过
  • AtomicStampedReference: 被动过几次

AtomicStampedReference

解决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
*/

AtomicIntegerArray

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
  }
}

AQS前情

在JAVA中,sun.misc.Unsafe 类提供了硬件级别的原子操作来实现这个CAS。 java.util.concurrent 包下的大量类都使用了这个 Unsafe.java 类的CAS操作。

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

yield+自旋
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个线程,想想会有什么问题?

sleep+自旋
volatile int status=0;
void lock(){
    while(!compareAndSet(0,1)){
        sleep(10);//时间设为固定,也很有问题
    }
    //拿锁成功
}

void unlock(){
    status=0;
}
park+自旋

这正是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

park的意思是停车,等待别人告诉可以开车再启动发动机unpark,一般是利用LockSupport类完成的,而他里面的park和unpart是native的

LockSupport.park();
LockSupport.unpark(Thread t);

AQS

概念

所谓AQS,指的是AbstractQueuedSynchronizer,它提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架。是除了java自带的synchronized关键字之外的锁机制。这个类在java.util.concurrent.locks包。

AQS是Java并发包提供的一个同步基础机制,是并发包中实现Lock和其他同步机制(如:Semaphore、CountDownLatch和FutureTask等)的基础。具体用法是通过继承AQS实现其模板方法,然后将子类作为同步组件的内部类。

  • 同步队列:AQS内部包含一个FIFO的同步等待队列,简单的说,没有成功获取控制权的线程会在这个队列中等待。

  • state:AQS内部管理了一个原子的int域作为内部状态信息,并提供了一些方法来访问该域,基于AQS实现的同步机制可以按自己的需要来灵活使用这个int域,比如:

    • ReentrantLock用它记录锁重入次数;
    • CountDownLatch用它表示内部的count;
    • FutureTask用它表示任务运行状态(Running,Ran和Cancelled);
    • Semaphore用它表示许可数量。
  • AQS内部提供了一个ConditionObject类来支持独占模式下的(锁)条件,这个条件的功能与Object的wait和notify/notifyAll的功能类似,但更加明确和易用。

  • AQS一般的使用方式为定义一个实现AQS接口的非公有的内部帮助类作为内部代理,来实现具体同步机制的方法,如Lock的lock和unlock;AQS中也提供一些检测和监控内部队列和条件对象的方法,具体同步机制可以按需使用这些方法;AQS内部只有一个状态,即原子int域,如果基于AQS实现的类需要做序列化/反序列化,注意这一点。

同步控制对比:

  • synchronized:基于JVM底层,基于C++,底层行为不可控
  • AbstractQueueSynchronizer:不利于任何jvm内置锁,基于java可变行为实现同步

AQS的核心思想是,

  • 如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,
  • 如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
    • CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系。
    • AQS是将每一条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node),来实现锁的分配。
    • 用大白话来说,AQS就是基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变state状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。

java多线程【4】AQS_第1张图片

AQS–队列

AQS的实现依赖内部的同步队列,也就是FIFO的双向队列,这个队列是用双线链表实现的

  • 如果当前线程竞争锁失败,那么AQS会把当前线程以及等待状态信息构造成一个Node,放入到同步队列尾部,作为尾结点,同时再阻塞该线程。
  • 当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。只需重新设置新的队列首部(头节点)即可。

java多线程【4】AQS_第2张图片

添加节点

当出现锁竞争以及释放锁的时候,AQS同步队列中的节点会发生变化,首先看一下添加节点的场景。

java多线程【4】AQS_第3张图片

这里会涉及到两个变化

  • 新的线程封装成Node节点追加到同步队列中,设置prev节点以及修改当前节点的前置节点的next节点指向自己
  • 通过CAS将tail重新指向新的尾部节点

释放锁 移除节点

head节点表示获取锁成功的节点,当头结点在释放同步状态时,会唤醒后继节点,如果后继节点获得锁成功,会把自己设置为头结点,节点的变化过程如下

java多线程【4】AQS_第4张图片

这个过程也是涉及到两个变化

  • 修改head节点指向下一个获得锁的节点
  • 新的获得锁的节点,将prev的指针指向null

这里有一个小的变化,就是设置head节点不需要用CAS,原因是设置head节点是由获得锁的线程来完成的,而同步锁只能由一个线程获得,所以不需要CAS保证,只需要把head节点设置为原首节点的后继节点,并且断开原head节点的next引用即可

看一下Node结点类

AQS–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–state

AQS中有一个这样的属性定义,这个对于重入锁的实现来说,表示一个同步状态。它有两个含义的表示

  • 当state=0时,表示无锁状态
  • 当state>0时,表示已经有线程获得了锁,也就是state=1,但是因为ReentrantLock允许重入,所以同一个线程多次获得同步锁的时候,state会递增,比如重入5次,那么state=5。 而在释放锁的时候,同样需要释放5次直到state=0其他线程才有资格获得锁
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域。下面来从请求和释放两条主线来进行相关代码分析。

分析ReentrantLock源码

先分析独占模式,下面是我们常用的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实例
}

NonfairSync

// 内部类
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);
    }
}

//-----------顺便对比一下公平锁和非公平锁-----------

FairSync

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;
    }
}

非公平锁和公平锁的区别再哪里呢?下图说明了公平和非公平的区别,记住一朝排队,永远排队。

java多线程【4】AQS_第5张图片

Sync

  • nonfairTryAcquire尝试拿锁,如果锁空闲或者完成锁重入,那么就代表拿锁成功。否则返回false拿锁失败
  • tryRelease:尝试释放,state状态值-1,减后如果state为0就释放
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
    }
}

AbstractQueuedSynchronizer.java

  • acquire获取锁
  • tryAcquire尝试获取锁,只是尝试,获取不到就回来。重入锁也算获取成功
  • addWaiter:没有获取锁成功,把当前线程放到队尾。
    • enq:给addWaiter服务用的,把操作队尾的cas循环和队列初始化放到这里了,addWaiter调用它enq
  • acquireQueued
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里,只有两个函数:

  1. public native void unpark(Thread jthread);
  2. 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.unlock

// 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
}

java多线程【4】AQS_第6张图片

java多线程【4】AQS_第7张图片

上面非公平锁分析完了,但哪里不公平了?

答案是线程刚进来要获取锁的时候,如果直接cas就是非公平了。如果先去排队尾再cas就公平

AQS底层使用了模板方法模式

同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用):

  1. 使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放)
  2. 将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。
    这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用。

自定义同步器在实现的时候只需要实现共享资源state的获取和释放方式即可,至于具体线程等待队列的维护,AQS已经在顶层实现好了。自定义同步器实现的时候主要实现下面几种方法:

  • isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
  • tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
  • tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
  • tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
  • tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
// 独占式获取锁
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

每个Condition包含一个等待队列,他是一个单向链表,但也维护这firstWaiter、lastWaiter

如果一个锁有两个condition,那么它就有两个队列

同步器维护这正常的双向链表,还维护这若干个conditoin单向队列

如果调用了await方法,就会把当前结点从同步队列里移动(重新包装)到condition等待队列中

如果调用了signal方法,就会加入到同步队列的尾部。

LockSupport

作用

  • 阻塞一个线程
  • 唤醒一个线程
  • 构建同步组件的基础工具
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

你可能感兴趣的:(多线程,Java,aqs)