java并发编程(7) 共享模型之工具 - AQS + ReentrantLock


前言

这篇文章讨论JDK中提供的 AQS接口以及其中一个实现类ReentrantLock。文章根据《Java并发编程的艺术》这本书以及黑马的视频 黑马多线程 做的笔记。


1. AQS

1. 概念

  1. 概述:全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架

  2. 特点:

    • 用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁

      ⅰ. getState - 获取 state 状态
      ⅱ. setState - 设置 state 状态
      ⅲ. compareAndSetState - cas 机制设置 state 状态
      ⅳ. 独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源

    1. 提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList
    2. 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet

子类主要实现这样一些方法(默认抛出 UnsupportedOperationException)

  • tryAcquire
  • tryRelease
  • tryAcquireShared
  • tryReleaseShared
  • isHeldExclusively
//获取锁的姿势
// 如果获取锁失败
if (!tryAcquire(arg)) {
 // 入队, 可以选择阻塞当前线程 aqs使用park unpark来恢复运行和禁止运行
}

//释放锁的姿势
// 如果释放锁成功
if (tryRelease(arg)) {
 // 让阻塞线程恢复运行
}

2. 代码

我们先自己实现一个不可重入锁,然后再去看源码

@Slf4j
//自定义不可重入锁
class MyLoack implements Lock{

    //独占锁, 同步器类
    class MySync extends AbstractQueuedSynchronizer{
        @Override
        protected boolean tryAcquire(int arg) {
            if(compareAndSetState(0, 1)){
                //加上了锁,并设置 owner 为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            //设置没有线程占用这个锁,exclusiveOwnerThread是独占模式同步的当前所有者
            setExclusiveOwnerThread(null);
            setState(0);    //把 volatile变量的写放在后面,让前面的 setExclusiveOwnerThread对其他线程可见
            return true;
        }

        @Override       //是不是持有独占锁
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        public Condition newCondition(){
            return new ConditionObject();
        }
    }

    private MySync sync = new MySync();

    @Override   //加锁
    public void lock() {
        sync.acquire(1);
    }

    @Override   //加锁,可打断锁
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override   //尝试获取锁
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override   //尝试获取锁 参数:时间
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override   //解锁
    public void unlock() {
        //release会唤醒等待的线程
        sync.release(1);
    }

    @Override   //设置一个新的容器
    public Condition newCondition() {
        return sync.newCondition();
    }
}

测试:

  MyLoack lock = new MyLoack();
        new Thread(()->{
            lock.lock();
            try {
                log.debug("加锁成功");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                log.debug("解锁成功");
                lock.unlock();
            }
        }, "t1").start();

        new Thread(()->{
            lock.lock();
            try {
                log.debug("加锁成功");
            }finally {
                log.debug("解锁成功");
                lock.unlock();
            }
        }, "t2").start();

结果输出:
在这里插入图片描述



2. ReentrantLock 原理

在这里插入图片描述


1. 非公平锁实现原理

从构造器来看,ReentrantLock模式实现的是非公平的锁
在这里插入图片描述

1. 加锁流程

1. 成功流程
public void lock() {
    sync.lock();
  }
 final void lock() {
 	//尝试把 state 从 0 改成 1
    if (compareAndSetState(0, 1))
    		//成功就设置线程是当前线程
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

在这里插入图片描述

2. 失败流程

在上面的基础上当又有一个线程尝试获取锁的时候,compareAndSetState(0, 1)就失败了,这时候进入 acquire(1)的流程

public final void acquire(int arg) {
  //首先tryAcquire再次尝试获取锁,如果获取失败了,进入acquireQueued流程
  if (!tryAcquire(arg) &&
         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
         selfInterrupt();
 }

这个方法中,首先tryAcquire再次尝试获取锁,如果获取失败了,进入acquireQueued流程,这个方法的流程就是创建一个节点加入等待队列中去,流程如下:

Thread-1 执行了

  1. lock方法中CAS 尝试将 state 由 0 改为 1,结果失败

  2. lock方法中进一步调用acquire方法,进入 tryAcquire 逻辑,这里我们认为这时 state 已经是1,结果仍然失败

  3. 接下来进入 acquire方法的addWaiter 逻辑,构造 Node 队列

    • 图中黄色三角表示该 Node 的 waitStatus 状态,其中 0 为默认正常状态
    • Node 的创建是懒惰的
    • 其中第一个 Node 称为 Dummy(哑元)或哨兵,用来占位,并不关联线程
      在这里插入图片描述
      在这里插入图片描述

 private Node addWaiter(Node mode) {
 	//创建节点,关联节点和当前线程
     Node node = new Node(Thread.currentThread(), mode);
     //获取尾节点
     Node pred = tail;
     //这一步和enq里面的实现差不多
     if (pred != null) {
     	//将尾节点和当前节点进行一个连接,注意下面是一个双向链表来的
         node.prev = pred;
         //连接 node <-> tail
         if (compareAndSetTail(pred, node)) {
             pred.next = node;
             return node;
         }
     }
     //尾节点为空,说明队列还未初始化,需要初始化head节点并入队新节点
     enq(node);
     return node;
 }



接下来,当前线程进入 acquire方法的 acquireQueued 逻辑

  1. acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞
  2. 如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,我们这里设置这时 state 仍为 1,失败
  3. 进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,这次返回 false。-1 表示这个节点有责任唤醒它的后继节点

在这里插入图片描述

  1. shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued ,再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
  2. 当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回 true
  3. 进入 parkAndCheckInterrupt, Thread-1 park(灰色表示已经阻塞)
    在这里插入图片描述

final boolean acquireQueued(final Node node, int arg) {
	//设置为 true
     boolean failed = true;
     try {
         boolean interrupted = false;
         for (;;) {
         	 //获取当前节点的前驱节点
             final Node p = node.predecessor();
             //判断如果前驱节点是头节点(占位节点),那么就会再尝试去获取一下锁
             if (p == head && tryAcquire(arg)) {
             	 //如果获取成功了锁
                 //如果是头节点就把当前节点设置为头,所以队列中可以看到就是第一个节点是
                 //工作的节点,第二个节点开始才是等待的节点,看上面的图也可以看出来
                 setHead(node);
                 p.next = null; //方便 GC 去回收旧的头节点
                 failed = false;
                 //返回 false,表示不能被打断
                 return interrupted;
             }
             //获取不到锁,下面是判断到底应不应该暂停这个线程
             if (shouldParkAfterFailedAcquire(p, node) &&
             	//下面是将当前线程挂起,这时候就发现不但将线程挂起,还给
             	//中断标志位设置为了 true,如果使用 wait,sleep,join 方法
             	//的任意一种,直接抛异常
                 parkAndCheckInterrupt())
                 //这个方法就阻塞在这里了,等到前面的线程唤醒的时候再向下运行
                 interrupted = true;
         }
     } finally {
         if (failed)
         	//这里只有抛出异常才可以进入
             cancelAcquire(node);
     }
 }


private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
	   //获取前驱节点的状态
        int ws = pred.waitStatus;
        //如果状态是 -1
        if (ws == Node.SIGNAL)
        	//这个节点已经设置了要求释放信号的状态, 所以可以安全的暂停
            return true;
        if (ws > 0) {
        	//如果大于0,此时把当前的node弄到第一位去
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
           //等于0(一开始等于0),旧给头节点设置状态为 -1 
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }


 //这个方法里面就是调用了 park 方法,并设置 interrupted 标记为为true
  private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }



2. 解锁流程

public void unlock() {
        sync.release(1);
    }
1. 成功解锁

现在假设很多个线程都竞争失败了,就变成下面这个样子
在这里插入图片描述
Thread-0 释放锁,进入 tryRealease 流程,如果成功

  • 设置 exclusiveOwnerThread 为 null
  • state = 0
    在这里插入图片描述
  • 如果当前队列头节点不为 null,并且 head 的 waitStatus = -1,进入 unparkSuccessor 流程: unparkSuccessor中会找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中即为 Thread-1
  • 此时回到 acquireQueued 这个方法,线程继续运行再次进入 for 循环,然后进行加锁
  public final boolean release(int arg) {
  //这里我们先不考虑里面的细节
  //直到里面设置了 exclusiveOwnerThread = null 并且设置了 stare 为0
    if (tryRelease(arg)) {
    	 //获取第一个节点
          Node h = head;
          //如果head不为null而且head的状态是0
          if (h != null && h.waitStatus != 0)
          	  //就唤醒下一个节点,然后在 acquireQueued 这个方法继续运行尝试加锁
              unparkSuccessor(h);
          return true;
      }
      return false;
  }

 private void unparkSuccessor(Node node) {
 
     	/**
     		省略前面一段代码
     	*/
        if (s != null)
        	//这里是调用了线程的 unpark 方法来唤醒的
            LockSupport.unpark(s.thread);
    }

如果加锁成功(没有其他i线程来和 Thread-1 竞争),会设置 (acquireQueued 方法中)

  • exclusiveOwnerThread 为 Thread-1,state = 1
  • head 指向刚刚 Thread-1 所在的 Node,该 Node 清空 Thread
  • 原本的 head 因为从链表断开,而可被垃圾回收
    在这里插入图片描述



2. 失败解锁

如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了,如果不巧被 Thread-4 占了先
在这里插入图片描述

  • Thread-4 被设置为 exclusiveOwnerThread,state = 1
  • Thread-1 再次进入 acquireQueued 这个方法的流程再次获取,然后重写进入 park 阻塞



2. 可重入原理

static final class NonfairSync extends Sync {
    // ...
	//获取锁的可重入
    // Sync 继承过来的方法, 方便阅读, 放在此处
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        //首先获取状态
        int c = getState();
        //如果是0,表示还没有人获取到锁
        if (c == 0) {
        	//那就从 0 改成 1
            if (compareAndSetState(0, acquires)) {
            	//然后设置exclusiveOwnerThread为当前线程
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入
        else if (current == getExclusiveOwnerThread()) {
            // state++
            //比如第二次,那么c就是1,这时候c+acquires就表示 state++
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }	
	
	//释放锁的可重入
    // Sync 继承过来的方法, 方便阅读, 放在此处
    protected final boolean tryRelease(int releases) {
        // state--,如果是重入的就减去一层 比如现在是 2,那么就是 2-1
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        // 支持锁重入, 只有 state 减为 0, 才释放成功
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        //如果是锁重入了,那么第一次返回false,直到锁重入次数减成0了才返回true表示释放成功
        return free;
    }
}



3. 可打断原理

1. 不可打断模式

不可打断模式:在此模式下,即使它被打断,仍会驻留在 AQS 队列中,等到获取锁的之后才能运行(继续运行,打断是指打断标记设置为 true,对于线程的运行是没有影响的)

我们看下面的代码:acquire 中尝试获取锁,然后进入了acquireQueued方法,这些都是前面说过的。我们看 acquireQueued 这个方法,在没有获取到锁的时候,就会被 park,当上一个线程执行完成之后就调用 unpark 方法唤醒了这个线程,然后这个线程向下执行 interrupted = true,但是 执行完了这个语句又会继续 for 循环一直等到获取到锁,这时候就可以进入 if (p == head && tryAcquire(arg))语句返回 interrupted = true 到 acquire中,而到了acquire中调用 selfInterrupt()继续尝生一个打断标记,因为 parkAndCheckInterrupt这个方法中的 Thread.interrupted会清除打断标记

// Sync 继承自 AQS
static final class NonfairSync extends Sync {
    // ...

    private final boolean parkAndCheckInterrupt() {
        // 如果打断标记已经是 true, 则 park 会失效
        LockSupport.park(this);
        // interrupted 会清除打断标记,清除是为了下次还可以park,否则如果有打断标记是不可以park的
        return Thread.interrupted();
    }

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null;
                    failed = false;
                    // 还是需要获得锁后, 才能返回打断状态
                    return interrupted;
                }
                if (
                        shouldParkAfterFailedAcquire(p, node) &&
                                parkAndCheckInterrupt()
                ) {
                    // 如果是因为 interrupt 被唤醒, 返回打断状态为 true
                    interrupted = true;
                }
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
	
    public final void acquire(int arg) {
        if (	
            
            	//这里能返回来进入if,证明上面的方法返回true了
                !tryAcquire(arg) &&
                        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
        ) {
            // 如果打断状态为 true
            selfInterrupt();
        }
    }

    static void selfInterrupt() {
        // 重新产生一次中断,这时候线程是如果正常运行的状态,那么不是出于sleep等状态,interrupt方法就不会报错
        Thread.currentThread().interrupt();
    }
}



2. 可打断模式

我们调用 acquireInterruptibly 这个方法而不是 acquire 方法来获取锁。这个方法里面 doAcquireInterruptibly 中如果获取不到锁还是会进入 park,而此时被其他线程唤醒之后就会直接抛出异常。所以如果在队列里面等待的时候被打断是会抛出异常的。

static final class NonfairSync extends Sync {
    public final void acquireInterruptibly(int arg) throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        // 如果没有获得到锁, 进入 ㈠
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

    // ㈠ 可打断的获取锁流程
    private void doAcquireInterruptibly(int arg) throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt()) {
                    // 在 park 过程中如果被 interrupt 会进入此
                    // 这时候抛出异常, 而不会再次进入 for (;;)
                    
                   //所以这里如果在队列里面等待的时候被打断是直接抛出异常的
                    throw new InterruptedException();
                }
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
}



4. 公平锁

1. 对比以下非公平锁

代码还是重入锁这段代码,可以看到非公平锁是不会去检查 AQS 队列的,而是直接 compareAndSetState。

 // ㈢ Sync 继承过来的方法, 方便阅读, 放在此处
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        // 如果还没有获得锁,这是第一次进入
        if (c == 0) {
            // 尝试用 cas 获得, 这里体现了非公平性: 不去检查 AQS 队列
            if (compareAndSetState(0, acquires)) {
            	//设置exclusiveOwnerThread为当前线程
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入锁
        else if (current == getExclusiveOwnerThread()) {
            // state++
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        // 获取失败, 回到调用处
        return false;
    }



2. 公平锁

tryAcquire方法中判断如果 AQS 队列没有前驱节点了,才允许当前线程去 CAS 获取锁

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;
    final void lock() {
        acquire(1);
    }

    // AQS 继承过来的方法, 方便阅读, 放在此处
    public final void acquire(int arg) {
        if (
                !tryAcquire(arg) &&
                        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
        ) {
            selfInterrupt();
        }
    }
    // 与非公平锁主要区别在于 tryAcquire 方法的实现
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 先检查 AQS 队列中是否有前驱节点, 没有才去竞争
            if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //下面这段也是重入的原理了,上面讲了
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

    // ㈠ AQS 继承过来的方法, 方便阅读, 放在此处
    public final boolean hasQueuedPredecessors() {
        Node t = tail;
        Node h = head;
        Node s;
        // h != t 时(头不等于尾)表示队列中有 Node, 证明有结点,如果自己的线程不是在老二(权值最高),老大是占位用的。
        return h != t &&
                (
                        //表示队列中还有没有老二
                        (s = h.next) == null || 
                        		//或者当前线程不是老二线程
                                s.thread != Thread.currentThread()
                );
    }
}



5. 条件变量实现原理

每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject


1. await 流程

开始 Thread-0 持有锁,调用 await,进入 ConditionObject(单链表) 的addConditionWaiter 流程 创建新的 Node 状态为 -2(Node.CONDITION),关联 Thread-0,加入等待队列尾部
在这里插入图片描述
接下来进入 AQS 的 fullyRelease 流程,释放同步器上的锁,并唤醒当前这个节点的后继节点
在这里插入图片描述
然后 Thread-0 unpark AQS 队列中的下一个节点,竞争锁,假设没有其他竞争线程,那么 Thread-1 竞争成功
在这里插入图片描述
park 阻塞 Thread-0
在这里插入图片描述


 public final void await() throws InterruptedException {
     //如果线程已经打断了,就抛出异常
     if (Thread.interrupted())
         throw new InterruptedException();
     //把节点添加到 ConditionObject 队列中
     Node node = addConditionWaiter();
     //fullyRelease:释放这个线程上面所有的锁,fullyRelease考虑到了对重入锁的释放
     int savedState = fullyRelease(node);
     int interruptMode = 0;
     //判断如果上面的节点是2而且是第一个
     while (!isOnSyncQueue(node)) {
     	 //当前线程就到这里阻塞住了
         LockSupport.park(this);
         if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
             break;
     }
     if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
         interruptMode = REINTERRUPT;
     if (node.nextWaiter != null) // clean up if cancelled
         unlinkCancelledWaiters();
     if (interruptMode != 0)
         reportInterruptAfterWait(interruptMode);
 }

//单链表的实现
 private Node addConditionWaiter() {
      Node t = lastWaiter;
      if (t != null && t.waitStatus != Node.CONDITION) {
          unlinkCancelledWaiters();
          t = lastWaiter;
      }
      //创建一个节点,状态设置为2
      Node node = new Node(Thread.currentThread(), Node.CONDITION);
      //连接到单链表去
      if (t == null)
          firstWaiter = node;
      else
          t.nextWaiter = node;
      lastWaiter = node;
      return node;
  }

//释放所有的锁
final int fullyRelease(Node node) {
     boolean failed = true;
     try {
         //这里是获取到状态,同时也是重入的次数
         int savedState = getState();
         //按重入的次数释放锁,在 failed 的过程中,会唤醒这个节点的后继节点
         if (release(savedState)) {
             failed = false;
             return savedState;
         } else {
             throw new IllegalMonitorStateException();
         }
     } finally {
         if (failed)
             node.waitStatus = Node.CANCELLED;
     }
 }



2. signal 流程

假设 Thread-1 要来唤醒 Thread-0
在这里插入图片描述
进入 ConditionObject 的 doSignal 流程,取得等待队列中第一个 Node,即 Thread-0 所在 Node
在这里插入图片描述
执行 transferForSignal 流程,将该 Node 加入 AQS 队列尾部,将 Thread-0 的 waitStatus 改为 0,Thread-3 的waitStatus 改为 -1。
在这里插入图片描述
Thread-1 释放锁,进入 unlock 流程,略


public final void signal() {
	//首先判断调用 signal 的线程是不是锁的持有者,只有 owner 线程才有资格去唤醒
   if (!isHeldExclusively())
        //不是就直接抛异常
        throw new IllegalMonitorStateException();
   //找到队首的元素
    Node first = firstWaiter;
    //调用doSignal
    if (first != null)
        doSignal(first);
}


 private void doSignal(Node first) {
     do {
          //找下一个元素,然后是 null,就把lastWaiter 设置为null,表示没有节点了
          //firstWaiter= first.nextWaiter:把下一个节点赋值为第一个节点
          if ( (firstWaiter = first.nextWaiter) == null)
              lastWaiter = null;
          first.nextWaiter = null;
          //transferForSignal:把上面的节点转移到等待队列里面
          //如果转移失败就还会去看有没有更多节点,如果有就再循环
          //有时候在等待队列中的元素被打断,就会被取消,这时候就返回了false
      } while (!transferForSignal(first) &&
               (first = firstWaiter) != null);
  }

 final boolean transferForSignal(Node node) {
        //设置state = 0,为什么是0呢,因为加到AQS竞争队列里面最后一个元素通常是0
        //其他元素会被改成 -1
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
		//enq方法就是把节点加入到 AQS 队列中,返回加入的这个节点的前驱节点,也就是
		//原来的尾节点
        Node p = enq(node);
        //获取前驱节点的状态
        int ws = p.waitStatus;
        //然后尝试把前驱节点的状态改成-1,表示前驱节点有义务唤醒新加入的尾节点
        //然后唤醒我们刚刚加入的节点,在上面的例子中
        //p = Thread-3, 加入的节点 - Thread-0
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }






如有错误,欢迎指出!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值