范文健康探索娱乐情感热点
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

聊聊AQS

  1、JUC的由来
  synchronized 关键字是JDK官方人员用C++代码写的,在JDK6以前是重量级锁。Java大牛  Doug Lea 对 synchronized 在并发编程条件下的性能表现不满意就自己写了个JUC,以此来提升并发性能,本文要讲的就是JUC并发包下的 AbstractQueuedSynchronizer 。
  在JUC中 CountDownLatch、ReentrantLock、ThreadPoolExecutor、ReentrantReadWriteLock 等底层用的都是AQS, AQS几乎占据了JUC并发包里的半壁江山, 如果想要获取锁可以被中断、超时获取锁、尝试获取锁那就用AQS吧 。
  DougLea杰作: HashMap、JUC、ConcurrentHashMap等。
  温馨提醒 :
  涉及到AQS重要方法、lock、unlock、CountDownLatch、await、signal几个重要组件的底层讲解所以内容有点长,嫌啰嗦的可直接看几个流程图即可(原图私信回复lock即可)。2、AQS前置知识点2.1、模板方法
  AbstractQueuedSynchronizer 是个抽象类,所有用到方法的类都要继承此类的若干方法,对应的设计模式就是模版模式。
  模版模式定义  :一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
  抽象类:  public abstract class SendCustom {  public abstract void to();  public abstract void from();  public void date() {   System.out.println(new Date());  }  public abstract void send();  // 注意此处 框架方法-模板方法  public void sendMessage() {   to();   from();   date();   send();  } }
  模板方法派生类:  public class SendSms extends SendCustom {   @Override  public void to() {   System.out.println("sowhat");  }   @Override  public void from() {   System.out.println("xiaomai");  }   @Override  public void send() {   System.out.println("Send message");  }    public static void main(String[] args) {   SendCustom sendC = new SendSms();   sendC.sendMessage();  } } 2.2、LookSupport
  LockSupport  是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,当然阻塞之后肯定得有唤醒的方法。常用方法如下:  public static void park(Object blocker); // 暂停当前线程 public static void parkNanos(Object blocker, long nanos); // 暂停当前线程,不过有超时时间的限制 public static void parkUntil(Object blocker, long deadline); // 暂停当前线程,直到某个时间 public static void park(); // 无期限暂停当前线程 public static void parkNanos(long nanos); // 暂停当前线程,不过有超时时间的限制 public static void parkUntil(long deadline); // 暂停当前线程,直到某个时间 public static void unpark(Thread thread); // 恢复当前线程 public static Object getBlocker(Thread t);
  叫 park 是因为 park 英文意思为停车。我们如果把 Thread 看成一辆车的话, park 就是让车停下, unpark 就是让车启动然后跑起来。
  与Object类的wait/notify机制相比,park/unpark有两个优点:  以  thread  为操作对象更符合阻塞线程的  直观定义
  操作更精准  ,可以准确地唤醒某一个线程(notify随机唤醒一个线程,notifyAll 唤醒所有等待的线程),增加了灵活性。
  park/unpark调用的是  Unsafe (提供CAS操作) 中的  native 代码。
  park/unpark 功能在Linux系统下是用的 Posix 线程库 pthread 中的 mutex (互斥量), condition (条件变量)来实现的。 mutex 和 condition 保护了一个  _counter  的变量,当  park  时,这个变量被设置为0。当 unpark 时,这个变量被设置为1。  2.3、CAS
  CAS 是 CPU指令级别实现了原子性的比较和交换(Conmpare And Swap)操作,注意CAS不是锁只是CPU提供的一个原子性操作指令。
  CAS在语言层面不进行任何处理,直接将原则操作实现在 硬件级别实现 ,之所以可以实现硬件级别的操作核心是因为CAS操作类中有个核心类 UnSafe 类。
  关于CAS引发的ABA问题、性能开销问题、只能保证一个共享变量之间的原则性操作问题,以前CAS中写过,在此不再重复讲解。
  注意 :并不是说 CAS 一定比SYN好,如果高并发执行时间久 ,用SYN好, 因为SYN底层用了wait() 阻塞后是不消耗CPU资源的。如果锁竞争不激烈说明自旋不严重,此时用CAS。  3、AQS重要方法
  模版方法分为 独占式 跟共享式 ,子类根据需要不同调用不同的模版方法(讲解有点多,想看底层可直接下滑到第四章节)。 3.1 模板方法3.1.1 独占式获取3.1.1.1 accquire
  不可中断获取锁 accquire 是获取独占锁方法,acquire 尝试获取资源,成功则直接返回,不成功则进入等待队列,这个过程不会被线程中断,被外部中断也不响应,获取资源后才再进行自我中断selfInterrupt() 。 public final void acquire(int arg) {         if (!tryAcquire(arg) &&             acquireQueued(addWaiter(Node.EXCLUSIVE), arg))             selfInterrupt(); } acquire(arg) tryAcquire(arg) 顾名思义,它就是尝试获取锁,需要我们自己实现具体细节,一般要求是:  如果该锁没有被另一个线程保持,则获取该锁并立即返回,将锁的保持计数设置为 1。
  如果当前线程已经保持该锁,则将保持计数加 1,并且该方法立即返回。
  如果该锁被另一个线程保持,则出于线程调度的目的,禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态,此时锁保持计数被设置为 1。   addWaiter(Node.EXCLUSIVE)
  主要功能是 一旦尝试获取锁未成功,就要使用该方法将其加入同步队列  尾部  ,由于可能有多个线程并发加入队尾产生竞争,因此采用 compareAndSetTail  锁方法来保证同步  acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
  一旦加入同步队列,就需要使用该方法,  自旋阻塞   唤醒来不断的尝试获取锁,直到被中断或获取到锁。  3.1.1.2 acquireInterruptibly
  可中断获取锁 acquireInterruptibly 相比于acquire 支持响应中断。 1、如果当前线程未被中断,则尝试获取锁。
  2、如果锁空闲则获锁并立即返回,state = 1。
  3、如果当前线程已持此锁,state + 1,并且该方法立即返回。
  4、如果锁被另一个线程保持,出于线程调度目的,禁用当前线程,线程休眠ing,除非锁由当前线程获得或者当前线程被中断了,中断后会抛出InterruptedException,并且清除当前线程的已中断状态。
  5、此方法是一个显式中断点,所以要优先考虑响应中断。    if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())      throw new InterruptedException(); // acquireInterruptibly 选择       interrupted = true; // acquire 的选择 3.1.1.3 tryAcquireNanos
  该方法可以被中断,增加了超时则失败的功能。可以说该方法的实现与上述两方法没有任何区别。时间功能上就是用的标准超时功能,如果剩余时间小于0那么 acquire 失败,如果该时间大于一次自旋锁时间(spinForTimeoutThreshold = 1000),并且可以被阻塞,那么调用LockSupport.parkNanos 方法阻塞线程。
  doAcquireNanos内部:    nanosTimeout = deadline - System.nanoTime();   if (nanosTimeout <= 0L)       return false;   if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold)       LockSupport.parkNanos(this, nanosTimeout);   if (Thread.interrupted())       throw new InterruptedException();
  该方法一般会有以下几种情况产生:  在指定时间内,线程获取到锁,返回true。
  当前线程在超时时间内被中断,抛中断异常后,线程退出。
  到截止时间后线程仍未获取到锁,此时线程获得锁失败,不再等待直接返回false。   3.1.2 共享式获取3.1.2.1 acquireShared    public final void acquireShared(int arg) {         if (tryAcquireShared(arg) < 0)             doAcquireShared(arg);     }
  该模版方法的工作:  调用 tryAcquireShared(arg)  尝试获得资源,返回值代表如下含义: 负数表示失败。
  0 表示成功,但没有剩余可用资源。
  正数表示成功,且有剩余资源。
  doAcquireShared作用:
  创建节点然后加入到队列中去,这一块和独占模式下的   addWaiter   代码差不多,不同的是结点的模式是SHARED,在独占模式 EXCLUSIVE。  3.1.2.2 acquireSharedInterruptibly
  无非就是可中断性的共享方法  public final void acquireSharedInterruptibly(long arg)  throws InterruptedException {     if (Thread.interrupted()) // 如果线程被中断,则抛出异常         throw new InterruptedException();     if (tryAcquireShared(arg) < 0)           // 如果tryAcquireShared()方法获取失败,则调用如下的方法         doAcquireSharedInterruptibly(arg); } 3.1.2.3. tryAcquireSharedNanos
  尝试以共享模式获取,如果被中断则中止,如果超过给定超时期则失败。实现此方法首先要检查中断状态,然后至少调用一次  tryacquireshared(long) ,并在成功时返回。否则,在成功、线程中断或超过超时期之前,线程将加入队列,可能反复处于阻塞或未阻塞状态,并一直调用 tryacquireshared(long) 。     public final boolean tryAcquireSharedNanos(long arg, long nanosTimeout)             throws InterruptedException {         if (Thread.interrupted())             throw new InterruptedException();         return tryAcquireShared(arg) >= 0 ||             doAcquireSharedNanos(arg, nanosTimeout);     } 3.1.3 独占式释放
  独占锁的释放调用 unlock 方法,而该方法实际调用了AQS的release 方法,这段代码逻辑比较简单,如果同步状态释放成功(tryRelease返回true)则会执行if块中的代码,当head指向的头结点不为null,并且该节点的状态值不为0的话才会执行unparkSuccessor() 方法。     public final boolean release(long arg) {         if (tryRelease(arg)) {             Node h = head;             if (h != null && h.waitStatus != 0)                 unparkSuccessor(h);             return true;         }         return false;     } 3.1.4 共享式释放
  releaseShared 首先去尝试释放资源tryReleaseShared(arg) ,如果释放成功了,就代表有资源空闲出来,那么就用doReleaseShared() 去唤醒后续结点。 public final boolean releaseShared(int arg) {     if (tryReleaseShared(arg)) {         doReleaseShared();         return true;     }     return false; }
  比如CountDownLatch的 countDown() 具体实现:     public void countDown() {         sync.releaseShared(1);     } 3.2 子类需实现方法
  子类 要实现 父类方法也分为 独占式 跟共享式 。 3.2.1 独占式获取
  tryAcquire 顾名思义,就是 尝试获取锁 ,AQS在这里没有对其进行功能的实现,只有一个抛出异常的语句,我们需要自己对其进行实现,可以对其重写实现公平锁、不公平锁、可重入锁、不可重入锁  protected boolean tryAcquire(int arg) {         throw new UnsupportedOperationException(); } 3.2.2 独占式释放
  tryRelease  尝试释放 独占锁,需要子类实现。    protected boolean tryRelease(long arg) {         throw new UnsupportedOperationException();     } 3.2.3 共享式获取
  tryAcquireShared  尝试进行共享锁的获得,需要子类实现。 protected long tryAcquireShared(long arg) {         throw new UnsupportedOperationException();     } 3.2.4 共享式释放
  tryReleaseShared 尝试进行共享锁的释放,需要子类实现。     protected boolean tryReleaseShared(long arg) {         throw new UnsupportedOperationException();     } 3.3 状态标志位
  state 因为用 volatile修饰 保证了我们操作的可见性,所以任何线程通过getState() 获得状态都是可以得到最新值,但是setState() 无法保证原子性,因此AQS给我们提供了compareAndSetState 方法利用底层UnSafe 的CAS功能来实现原子性。     private volatile long state;      protected final long getState() {         return state;     }      protected final void setState(long newState) {         state = newState;     }     protected final boolean compareAndSetState(long expect, long update) {         return unsafe.compareAndSwapLong(this, stateOffset, expect, update);     } 3.4 查询是否独占模式
  isHeldExclusively  该函数的功能是查询当前的工作模式是否是独占模式。需要子类实现。     protected boolean isHeldExclusively() {         throw new UnsupportedOperationException();     } 3.5 自定义实现锁
  这里需要重点说明一点, JUC中一般是用一个子类继承自Lock,然后在子类中定义一个内部类来实现AQS的继承跟使用 。  public class SowhatLock implements Lock {  private Sync sync = new Sync();   @Override  public void lock()  {   sync.acquire(1);  }   @Override  public boolean tryLock()  {   return false;  }   @Override  public boolean tryLock(long time, TimeUnit unit) throws InterruptedException  {   return sync.tryAcquireNanos(1,unit.toNanos(time));  }   @Override  public void unlock()  {   sync.release(1);  }   @Override  public Condition newCondition()  {   return sync.newCondition();  }   @Override  public void lockInterruptibly() throws InterruptedException  {  }   private class Sync extends AbstractQueuedSynchronizer  {   @Override   protected boolean tryAcquire(int arg)   {    assert arg == 1;    if (compareAndSetState(0, 1))    {     setExclusiveOwnerThread(Thread.currentThread());     return true;    }    return false;   }    @Override   protected boolean tryRelease(int arg)   {    assert arg == 1;    if (!isHeldExclusively())    {     throw new IllegalMonitorStateException();    }    setExclusiveOwnerThread(null);    setState(0);    return true;   }    @Override   protected boolean isHeldExclusively()   {    return getExclusiveOwnerThread() == Thread.currentThread();   }    Condition newCondition() {    return new ConditionObject();   }  } }
  自定义实现类:  public class SoWhatTest {  public static int m = 0;  public  static CountDownLatch latch  = new CountDownLatch(50);  public static Lock lock = new SowhatLock();   public static void main(String[] args) throws  Exception  {   Thread[] threads = new Thread[50];   for (int i = 0; i < threads.length ; i++)   {    threads[i] = new Thread(()->{     try{      lock.lock();      for (int j = 0; j <100 ; j++)      {       m++;      }     }finally     {      lock.unlock();     }     latch.countDown();   });   }   for(Thread t : threads) t.start();   latch.await();   System.out.println(m);  } } 4、AQS底层4.1 CLH
  CLH (Craig、 Landin、 Hagersten locks三个人名字综合而命名):  是一个  自旋锁  ,能确保无饥饿性,提供先来先服务的公平性。
  CLH   锁也是一种 基于链表  的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。  4.2 Node
  CLH  队列由 Node 对象组成,其中Node是AQS中的内部类。  static final class Node {  // 标识共享锁  static final Node SHARED = new Node();  // 标识独占锁  static final Node EXCLUSIVE = null;  // 前驱节点  volatile Node prev;  // 后继节点  volatile Node next;  // 获取锁失败的线程保存在Node节点中。  volatile Thread thread;  // 当我们调用了Condition后他也有一个等待队列  Node nextWaiter;  //在Node节点中一般通过waitStatus获得下面节点不同的状态,状态对应下方。  volatile int waitStatus;  static final int CANCELLED =  1;  static final int SIGNAL    = -1;  static final int CONDITION = -2;  static final int PROPAGATE = -3;
  waitStatus  有如下5中状态:  CANCELLED = 1
  表示当前结点已取消调度。当超时或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。   SIGNAL = -1
  表示后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为 SIGNAL。   CONDITION = -2
  表示结点等待在 Condition 上,当其他线程调用了 Condition 的 signal() 方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。   PROPAGATE = -3
  共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。   INITIAL = 0
  新结点入队时的默认状态。   4.3 AQS实现4.3.1 公平锁和非公平锁
  银行售票窗口营业中:  公平排队  :每个客户来了自动在最后面排队,轮到自己办理业务的时候拿出身份证等证件取票。
  非公平排队  :有个旅客火车马上开车了,他拿着自己的各种证件着急这想跟窗口工作人员说是否可以加急办理下,可以的话则直接办理,不可以的话则去队尾排队去。
  在JUC中同样存在 公平锁 跟非公平锁 ,一般非公平锁效率好一些。因为非公平锁状态下打算抢锁的线程不用排队挂起了。 4.3.2 AQS细节
  AQS 内部维护着一个 FIFO 的队列,即 CLH 队列,提供先来先服务的公平性。 AQS 的同步机制就是依靠 CLH 队列实现的。 CLH 队列是 FIFO 的双端双向链表队列(方便尾部节点插入)。线程通过 AQS 获取锁失败,就会将线程封装成一个 Node 节点,通过 CAS 原子操作插入队列尾。当有线程释放锁时,会尝试让队头的next节点占用锁,个人理解AQS具有如下几个特点:  在AQS 同步队列中 -1 表示线程在睡眠状态
  当前Node节点线程会把前一个Node.ws = -1。当前节点把前面节点ws设置为-1,你可以理解为:你自己能知道自己睡着了吗?只能是别人看到了发现你睡眠了  !
  持有锁的线程永远不在队列中。
  在AQS队列中第二个才是最先排队的线程  。
  如果是交替型任务或者单线程任务,即使用了Lock也不会涉及到AQS 队列  。
  不到万不得已不要轻易park线程,很耗时的!所以排队的头线程会自旋的尝试几个获取锁  。
  4.4 加锁跟解锁流程图
  以最经典的  ReentrantLock  为例逐步分析下  lock  跟  unlock  底层流程图(要原图的话私信回复: lock )。 private Lock lock = new ReentrantLock(); public void test(){     lock.lock();     try{         doSomeThing();     }catch (Exception e){       ...     }finally {         lock.unlock();     } }
  4.4.1 独占式加入同步队列
  同步器AQS中包含两个节点类型的引用:一个指向头结点的引用(head),一个指向尾节点的引用(tail),如果加入的节点是OK的则会直接运行该节点,当若干个线程抢锁失败了那么就会 抢着加入 到同步队列的尾部,因为是抢着加入这个时候用 CAS 来设置尾部节点。入口代码:  public final void acquire(int arg) {     if (!tryAcquire(arg) &&         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))         selfInterrupt(); } tryAcquire
  该方法是需要自我实现的,在上面的demo中可见一斑,就是返回是否获得了锁。    protected final boolean tryAcquire(int acquires) {      final Thread current = Thread.currentThread();      int c = getState();      if (c == 0) {          //  是否需要加入队列,不需要的话则尝试CAS获得锁,获得成功后 设置当前锁的拥有者          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;  } addWaiter(Node.EXCLUSIVE,arg)  /**  * 如果尝试获取同步状态失败的话,则构造同步节点(独占式的Node.EXCLUSIVE),通过addWaiter(Node node,int args)方法将该节点加入到同步队列的队尾。  */  private Node addWaiter(Node mode) {      // 用当前线程构造一个Node对象,mode是一个表示Node类型的字段,或者说是这个节点是独占的还是共享的      Node node = new Node(Thread.currentThread(), mode);      // 将目前队列中尾部节点给pred      Node pred = tail;      // 队列不为空的时候      if (pred != null) {          node.prev = pred;          // 先尝试通过AQS方式修改尾节点为最新的节点,如果修改失败,意味着有并发,          if (compareAndSetTail(pred, node)) {              pred.next = node;              return node;          }      }      //第一次尝试添加尾部失败说明有并发,此时进入自旋      enq(node);      return node;  } 自旋enq   方法将并发添加节点的请求通过CAS跟自旋将尾节点的添加变得 串行化 起来。说白了就是让节点放到正确的队尾位置。  /** * 这里进行了循环,如果此时存在了tail就执行同上一步骤的添加队尾操作,如果依然不存在, * 就把当前线程作为head结点。插入节点后,调用acquireQueued()进行阻塞 */ private Node enq(final Node node) {    for (;;) {        Node t = tail;        if (t == null) { // Must initialize            if (compareAndSetHead(new Node()))                tail = head;        } else {            node.prev = t;            if (compareAndSetTail(t, node)) {                t.next = node;                return t;            }        }    } } acquireQueued   是当前Node节点线程在死循环中获取同步状态,而只有前驱节点是 头节点 才能尝试获取锁,原因是:  头结点是成功获取同步状态(锁)的节点,而头节点的线程释放了同步状态以后,将会唤醒其后继节点,后继节点的线程被唤醒后要检查自己的前驱节点是否为头结点。
  维护同步队列的  FIFO  原则,节点进入同步队列之后,会尝试自旋几次。  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)) {             // 节点中的线程循环的检查,自己的前驱节点是否为头节点             // 只有当前节点 前驱节点是头节点才会 再次调用我们实现的方法tryAcquire             // 接下来无非就是将当前节点设置为头结点,移除之前的头节点                 setHead(node);                 p.next = null; // help GC                 failed = false;                 return interrupted;             }             // 否则检查前一个节点的状态,看当前获取锁失败的线程是否要挂起             if (shouldParkAfterFailedAcquire(p, node) &&            //如果需要挂起,借助JUC包下面的LockSupport类的静态方法park挂起当前线程,直到被唤醒                 parkAndCheckInterrupt())                 interrupted = true; // 两个判断都是true说明 则置true         }     } finally {         //如果等待过程中没有成功获取资源(如timeout,或者可中断的情况下被中断了),那么取消结点在队列中的等待。         if (failed)            //取消请求,将当前节点从队列中移除             cancelAcquire(node);     } }
  如果成功就返回,否则就执行 shouldParkAfterFailedAcquire 、parkAndCheckInterrupt 来达到阻塞效果。 shouldParkAfterFailedAcquire 第二步的 addWaiter() 构造的新节点,waitStatus 的默认值是0 。此时,会进入最后一个if判断,CAS设置pred.waitStatus SIGNAL ,最后返回false 。由于返回false ,第四步的acquireQueued 会继续进行循环。假设node 的前继节点pred 仍然不是头结点或锁获取失败,则会再次进入shouldParkAfterFailedAcquire() 。上一轮循环中已经将pred.waitStatu = -1 了,则这次会进入第一个判断条件,直接返回true ,表示应该阻塞调用parkAndCheckInterrupt 。
  那么什么时候会遇到 ws > 0 呢?当pred 所维护的获取请求被取消时(也就是node.waitStatus = CANCELLED,这时就会循环移除所有被取消的前继节点pred,直到找到未被取消的pred。移除所有被取消的前继节点后,直接返回false。 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {        int ws = pred.waitStatus; // 获得前驱节点的状态        if (ws == Node.SIGNAL) //此处是第二次设置            return true;        if (ws > 0) {           do {                node.prev = pred = pred.prev;            } while (pred.waitStatus > 0);            pred.next = node;        } else {           //  此处是第一次设置 unsafe级别调用设置           compareAndSetWaitStatus(pred, ws, Node.SIGNAL);        }        return false;    } parkAndCheckInterrupt 主要任务是暂停当前线程然后查看是否已经暂停了。  private final boolean parkAndCheckInterrupt() {     // 调用park()使线程进入挂起状态,什么时候调用了unpark再继续执行下面     LockSupport.park(this);      // 如果被唤醒,查看自己是不是已经被中断了。     return Thread.interrupted(); } cancelAcquire acquireQueued 方法的finally会判断 failed 值,正常运行时候自旋出来的时候会是false ,如果中断或者timeout 了 则会是true ,执行cancelAcquire ,其中核心代码是node.waitStatus = Node.CANCELLED 。 selfInterrupt  static void selfInterrupt() {       Thread.currentThread().interrupt();   } 4.4.2 独占式释放队列头节点
  release() 会调用tryRelease 方法尝试释放当前线程持有的锁,成功的话唤醒后继线程,并返回true,否则直接返回false。 public final boolean release(long arg) {     if (tryRelease(arg)) {         Node h = head;         if (h != null && h.waitStatus != 0)             unparkSuccessor(h);         return true;     }     return false; } tryRelease 这个是子类需要自我实现的,没啥说的根据业务需要实现。  unparkSuccessor 唤醒头结点的后继节点。  private void unparkSuccessor(Node node) {    int ws = node.waitStatus; // 获得头节点状态     if (ws < 0) //如果头节点装小于0 则将其置为0         compareAndSetWaitStatus(node, ws, 0);     Node s = node.next; //这个是新的头节点     if (s == null || s.waitStatus > 0) {      // 如果新头节点不满足要求         s = null;         for (Node t = tail; t != null && t != node; t = t.prev)         //从队列尾部开始往前去找最前面的一个waitStatus小于0的节点             if (t.waitStatus <= 0)                 s = t;     }     if (s != null)//唤醒后继节点对应的线程         LockSupport.unpark(s.thread); } 4.4.3 AQS 中增加跟删除形象图
  5、CountDownLatch底层5.1 共享锁 CountDownLatch底层
  CountDownLatch  虽然相对简单,但也实现了共享锁模型。但是如何正确的吹逼 CountDownLatch 呢?如果在理解了上述流程的基础上,从 CountDownLatch 入手来看  AQS  中关于 共享锁 的代码还比较好看懂,在看的时候可以  以看懂大致内容为主,学习其设计的思路 ,不要陷入所有条件处理细节中,多线程环境中,对与错有时候不是那么容易看出来的。个人追源码绘制了如下图:
  5.2 计数信号量Semaphore
  Semaphore 这就是共享锁的一个实现类,在初始化的时候就规定了共享锁池的大小N,有一个线程获得了锁,可用数就减少1个。有一个线程释放锁可用数就增加1个。如果有 >=2 的线程同时释放锁,则此时有多个锁可用。这个时候就可以  同时唤醒  两个锁 setHeadAndPropagate  (流程图懒的绘制了)。   public final void acquireShared(int arg) {      if (tryAcquireShared(arg) < 0)          doAcquireShared(arg);  } private void doAcquireShared(int arg) {     final Node node = addWaiter(Node.SHARED);     boolean failed = true;     try {         boolean interrupted = false;         for (;;) {             //找先驱结点             final Node p = node.predecessor();             if (p == head) {                  // 尝试获取资源                 int r = tryAcquireShared(arg);                 if (r >= 0) {                     // 设置当前结点为头结点,然后去唤醒后续结点。注意传播性 唤醒!                     setHeadAndPropagate(node, r);                     p.next = null; // help GC  释放头结点,等待GC                     if (interrupted)                         selfInterrupt();                     failed = false;//获取到资源                     return;                 }             }             if (shouldParkAfterFailedAcquire(p, node) &&                 parkAndCheckInterrupt())                 interrupted = true;         }     } finally {         if (failed)//如果最后没有获取到资源,则cancel             cancelAcquire(node);     } } 5.3 ReentrantReadWriteLock
  在 ReentrantReadWriteLock 类中也是只有一个32位的 int state 来表示读锁跟写锁,如何实现的? 后16位用来保存独享的  写锁  个数,第一次获得就是01,第二次重入就是10了,这样的方式来保存。
  但是多个线程都可以获得读锁,并且每个线程可能读多次,如何保存?我们用前16位来保存有多少个线程获得了  读锁  。
  每个读锁线程获得的重入读锁个数 由内部类  HoldCounter  与读锁配套使用。  6、Condition
  synchronized 可用  wait()  和  notify() / notifyAll()  方法相结合可以实现等待/通知模式。 Lock  也提供了  Condition  来提供类似的功能。
  Condition 是JDK5后引入的Interface ,它用来替代传统的Object的wait()/notify() 实现线程间的协作,相比使用Object的wait()/notify() ,使用Condition 的await()/signal() 这种方式 实现线程间协作更加安全和高效。简单说,他的作用是使得某些线程一起等待某个条件(Condition),只有当该条件具备(signal 或者 signalAll方法被调用)时,这些等待线程才会被唤醒,从而重新争夺锁。wait()/notify() 这些都更倾向于底层的实现开发,而Condition接口更倾向于代码实现的等待通知效果。两者之间的区别与共通点如下:
  6.1 条件等待队列
  条件等待队列,指的是 Condition 内部自己维护的一个队列,不同于 AQS 的 同步等待队列。它具有以下特点:  要加入条件等待队列的节点,不能在 同步等待队列。
  从 条件等待队列 移除的节点,会进入同步等待队列。
  一个锁对象只能有一个同步等待队列,但可以有多个条件等待队列。
  这里以  AbstractQueuedSynchronizer  的内部类  ConditionObject  为例(Condition 的实现类)来分析下它的具体实现过程。首先来看该类内部定义的几个成员变量:  /** First node of condition queue. */ private transient Node firstWaiter; /** Last node of condition queue. */ private transient Node lastWaiter;
  它采用了  AQS  的  Node  节点构造(前面说过 Node 类有 nextWaiter 属性),并定义了两个成员变量: firstWaiter 、 lastWaiter  。说明在 ConditionObject 内部也维护着一个自己的单向等待队列。目前可知它的结构如下:
  6.2 await、signal
  比如有线程 1、2竞争锁,下面来说下具体过程 线程1:  1、线程1 调用 reentrantLock.lock时,持有锁。
  2、线程1 调用 await 方法,进入条件  等待队列   ,同时释放锁。
  3、线程1 获取到线程2 signal 信号,从条件等待队列进入同步等待队列。
  线程2:  1、线程2 调用 reentrantLock.lock时,由于锁被线程1 持有,进入同步  等待队列   。
  2、由于线程1 释放锁,线程2 从同步等待队列 移除,获取到锁。
  3、线程2 调用 signal 方法,导致线程 1 被唤醒。线程2 调用unlock ,线程1 获取锁后继续下走。   6.2.1 await
  当我们看await、signal 的源码时候不要认为等待队列跟同步队列是完全分开的,其实个人感觉底层源码是有点 HashMap 中的红黑树跟双向链表的意思。
  当调用await方法时候,说明当前任务队列的头节点拿着锁呢,此时要把该Thread从任务队列挪到等待队列再唤醒任务队列最前面排队的运行任务,如图:
  thread 表示节点存放的线程。  waitStatus 表示节点等待状态。条件等待队列中的节点等待状态都是 CONDITION,否则会被清除。  nextWaiter 表示后指针。  6.2.2 signal
  当我们调用signal方法的时候,我们要将等待队列中的头节点移出来,让其去抢锁,如果是公平模式就要去排队了,流程如图:
  上面只是形象流程图,如果从代码级别看的话大致流程如下:
  6.2.3 signalAll
  signalAll  与  signal  方法的区别体现在  doSignalAll  方法上,前面我们已经知道doSignal方法只会对等待队列的 头节点 进行操作, doSignalAll 方法只不过将等待队列中的 每一个节点 都移入到同步队列中,即 通知 当前调用condition.await()方法的每一个线程: private void doSignalAll(Node first) {     lastWaiter = firstWaiter = null;     do {         Node next = first.nextWaiter;         first.nextWaiter = null;         transferForSignal(first);         first = next;     } while (first != null); // 循环 } 6.3 End
  一个 Condition 对象就有一个单项的 等待任务队 列。在一个多线程任务中我们可以new出多个等待任务队列。比如我们new出来两个等待队列。   private Lock lock = new ReentrantLock();  private Condition FirstCond = lock.newCondition();  private Condition SecondCond = lock.newCondition();
  所以真正的AQS任务中一般是 一个任务队列N个等待队列的 ,因此我们尽量调用 signal 而少用 signalAll ,因为在指定的实例化等待队列中只有一个可以拿到锁的。
  Synchronized 中的  wait  跟  notify  底层代码的等待队列只有一个,多个线程调用 wait 的时候我们是无法知道头节点是那个具体线程的。因此只能 notifyAll 。  7、参考1、详解Condition的await和signal:https://www.jianshu.com/p/28387056eeb4
  2、Condition的await和signal流程:https://www.cnblogs.com/insaneXs/p/12219097.html
  链接:https://mp.weixin.qq.com/s/XxhBE5tb1uuIp8LnMQoPZQ

华强北的苹果耳机,太逆天了相信机友们都知道,这几年AirPods已经成为苹果所有业务当中,利润率较高的一个。前段时间有调查机构专门做了评估,去年苹果光是靠它,就赚了230。5亿美元,将近1500亿元人民币。孩子玩手机不写作业,家长不能只批评!云朵守护这么做才有效当孩子只想玩手机不想写作业的时候,我们做家长的一定要控制好自己的情绪,不要愤怒呵斥孩子引起孩子的逆反心理,也一定不要说过软的话用苦情计来引起孩子的愧疚让孩子去学习。孩子沉迷手机游戏互换二十八之姚策姚策作为互换二十八的当事人之一已逝去,他那么孤单地离开了这个世界,带着那么多遗憾和心事走了,他是死不瞑目啊!曾经的他,也是一个好孩子,当得知癌细胞已扩散,他坚持不去做化疗,选择保守乾坤未定,你我皆是黑马你有没有借钱的经历?特别是你在最无助最需要的时候向亲朋好友借钱时,他们都以各种理由推托?是的,我有过这种经历。当时的我特别无地自容,就想如果哪里有地缝马上钻进去。这时我才看清了身边地球位于银河系的边缘区域,对人类来说是好事吗?银河系的直径大约有10万光年,恒星数量更是高达1000亿到4000亿,其中太阳系位于银河系比较偏远的位置,这对于地球和人类来说,是好事还是坏事呢?太阳系是一个呈杆状螺旋状的星系,有凌晨,恒大发布最新公告10月9日凌晨,恒大集团官网公告,集团6名提前赎回恒大财富投资产品的管理人员,已于10月8日前将所有提前赎回的款项全额归还到恒大财富指定账户。恒大集团同时表示,公司已对上述6名管理一个人在这3件事上大方越容易穷,人缘也会越差,你经历过吗?前言不知道大家有没有注意到这样一个现象,就是一个人,明明有很努力的在工作,但是身上总是存不下什么钱,每天都在嚷嚷自己好穷,可是钱到底花到哪里去了呢,仔细想想也想不出个所以然。现象不致十月把自己照顾好,比什么都重要十月风凉,吹落一地枯黄。不知觉间,时光一晃就到了深秋。过去的日子,你为了生活四处奔波,常常把更多精力用来关心别人,却唯独忘了照顾自己。刘德华说过好好照顾自己,才有好的未来。人一辈子RedmiK50深度曝光,防水120W快充双扬声器等应有尽有根据消息,今年的小米12系列手机和RedmiK50系列手机可能都会提前到来,小米12系列手机可能在12月底发布,随后RedmiK50系列手机也将发布,而目前小米RedmiK50系列iPhone14外观改变,采用挖孔设计方案,A16芯片2TB大内存今年智能手机最火的机型肯定是iPhone13系列,因为这次苹果也有了性价比,在产品本身有着极大的市场竞争力,依然是高端手机市场王者级别的存在!虽然iPhone13标准版在硬件方面提iPhone13与OnePlus9对比区别很明显iPhone13带来了更强大的处理器和更大的电池,以及电影效果模式在手机摄影领域也堪称一绝。这边OnePlus与相机专家哈苏合作,改进相机性能的同时,OnePlus9也获得了质的飞
2022难忘旅行(一)八月初作为旅游人接到了一个新任务当最后一批研学旅行的带队导师助理。作为很少带小孩子团的我来说这次是一个很大的挑战,虽然心里紧张却还是镇定地接受了领导安排的任务。领导决定给我安排这项不小心翻开了他们的相册,收获一张旅行清单拿起背包,手握相机,关于来一场说走就走的旅行这件事儿,是不是已经被搁置很久了!但最近小编在逛小组发现一件有趣的事儿,越来越多的友友们开始翻开自己的相册,晒出自己曾经走过的足迹与城市来泸沽湖旅行,你一定要知道这些事!摩梭人的禁忌陈洪波摄一汪碧蓝如镜的湖泊,让泸沽湖闻名于世界一首首古老嘹亮的民谣,让摩梭文化被大众熟知。去一个地方旅游,不止是欣赏大自然的美好,还要深入当地人的生活,了解独具特色的民广东宏远VS宁波富邦!杜锋冲击三连胜,拒绝爆冷广东宏远取得两连胜面对实力不俗的天津队,广东队几乎全场压制,防守非常出色,只让天津队得到了83分。马尚布鲁克斯无人可挡,命中了7记三分球,狂砍29分14篮板8助攻。广东队最大的收获若有来生,我一定等你慢慢地长大郝有花(图片来自网络)脸颊上滑过一丝湿润如一股暖流,暖暖的那不是别离后的难过是那些无法捡拾的美好,在嘶鸣花开的故事,在流年里又一次发芽在指尖歌唱,在酒里晃荡如眉宇间的褶皱,轻轻一抚梦幻尘缘,电光火石,如水中月,如雾中花人生苦短别经年,离多聚少奈何天。此心此情无长计,今宵月圆人未圆。梦幻尘缘,电光火石,如水中月,如雾中花。和她的相遇就是缘分,我们的这场缘分来之不易。那一天,我到市场街闲逛。看着人来任静丨秋日暖阳秋日暖阳文任静秋日,闲适的午后,小花园内,几株高大的女贞树泛着苍绿的叶片,斜斜地插入云端,连同粉色的住宅楼,将天空衬托得愈加澄碧瓦蓝,梧桐树的叶子落光了,苍劲的枝桠伸向天空,仿佛一距离就是尊重亲人之间,距离是尊重爱人之间,距离是美丽朋友之间,距离是爱护同事之间,距离是友好陌生人之间,距离是礼貌。别小看这些生活里的距离,有多少情感都最终败在距离上。远了生出不满,近了又生出世界因不同而相同人和人之间最难的是沟通,理解。每个人最累的是思维,观念的不同。在这个亿万个不同的个体中,找出我们的共同点,交流交流也美哉。不同点,互相交换,没必要谁强加给谁。可最可悲的就是忠言逆耳如何把一件想做的事做成?很简单,只要你懂得建立自己的成功周期你有没有试过,想做一件什么事,往往很容易半途而废?这不是你的错,这只不过是我们的人性使然,毕竟人都是趋利避害的,既然努力对我们来说是一种伤害,自然就驱使我们躲避。问题是,如果你真的高手都是顺应人性的文晴天小朱图来源于网络,侵删高手都是顺应人性的,反而生活困顿之人反人性晴天小朱我们市面上听到的都是厉害的人,成大事的人都是反人性的人性是什么?刘润的底层逻辑里解释人性只涉及生存和繁5场0分,杜锋不满意了!不让昔日射手进入比赛名单,把他打入冷宫昨晚第七轮比赛,广东队迎来了一个弱旅宁波队,最终不出意料地大胜对手,取得了三连胜,在积分榜也冲至前八。也许是对面是超级鱼腩,一开始广东队在防守端比较松散,让宁波队紧紧咬住比分,并在西甲创下最差表现,仅剩皇马扛大旗文羊城晚报全媒体记者刘毅欧冠小组赛第5轮10月27日凌晨全部结束,首次出现3支西甲球队在欧冠小组赛出局的情况,巴塞罗那马德里竞技塞维利亚只能以小组第三的身份转战欧联杯。这样一来,本直降1420,小米12Pro天玑版值得入手吗?在今年7月份,小米发布了小米12Pro天玑版,这款手机搭载联发科天玑9000的处理器,当时的发售价仅为3999元。如今临近双11,这款手机的价格也迎来了新低,最高直降1420,25现代最新技术在短短7小时内将贫瘠的沙漠沙子变成肥沃的土壤挪威初创公司DesertControl找到了防止沙漠化的有效解决方案,方法是将沙子与液态纳米粘土(LNC)混合,在短短7小时内将其变成肥沃的土壤。根据OddityCentral(英万众瞩目的华为,下一步棋来了,开赴5。5G时代了华为5G的下一个演进方向终于是来了,这项新技术让用户体验是提升的10倍,再次打的老美是措手不及,就在不久前华为的轮值董事长胡厚崑官宣,华为将全面迈向5。5g技术,在5。5g时代华为藏不住了!大降价提上日程,网友买手机能便宜吗?最近,最热门的话题就是双11,给消费者带来了新的关注点,虽然现在套路越来越多,但是双11确实有一些产品会降价,我觉得可以对比三家,找出优惠力度最大的品牌和平台。这内容不是广告也不是江苏内河运输开启纯电动时代船舶产业迎来转型发展新空间全国首艘120标箱纯电动内河集装箱船江远百合号不久前在苏州太仓投用,往返于太仓港和京杭运河苏州工业园区港。作为航运新历史的见证者,船长何广科兴奋地表示,电动船实现零排放零污染,目前倪光南院士称自研芯片应在RISCV上押宝!为什么说这种芯片是中国的重要机会?近日,中国工程院院士倪光南撰文指出中国自研芯片要用好开源模式,聚焦RISCV架构。RISCV架构在国内外发展态势如何?为什么说开源芯片对中国是一次重要的机会?近日,中国工程院院士倪嫁70多岁法国老头,甘愿成为他第四任妻子,巩俐到底在追求什么?2019年,让米歇尔雅尔刚刚拿到体检报告,没走出医院大门,就迫不及待地把报告结果发给了巩俐,并附带了一段解说文字我是七十岁的年龄,五十岁的身体,四十岁的心态,我一定能给你幸福。巩俐澄江化石地入选首批地质遗产地,专家化石保存完整,世界少有1984年6月,中国科学院南京地质古生物研究所助理研究员侯先光来到我国云南澄江帽天山附近,寻找古生物化石标本。他清楚地记得,当年7月1日下午两点多,他发现了第一块保存软躯体的纳罗虫100多名越南游客入境韩国后失联两国政府紧急寻人来源海外网越南外交部发言人黎氏秋姮(越通社)海外网10月27日电据韩国朝鲜日报10月27日援引越通社消息,近日100多名越南游客免签入境韩国后失联,对此越南外交部予以回应。韩国襄阳