AQS

1。AQS是什么

2。AQS 用法,demo 效果

3 。AQS 原理,为什么能干成这样

4。AQS的扩展

什么是AQS

名词解释:JUC下 一个名为AbstractQueuedSynchronizer的类(java.util.concurrent.locks.AbstractQueuedSynchronizer)简称 AQS

功能简介:队列同步器,用来构建锁或者其他同步组件的基础框架。它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作,作者 Doug lean,一个非常的老头。

img

AQS 使用效果

package com.emma.wangzhifeng.tr.juc;


import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class Mutex implements Lock {

    private static class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        @Override
        protected boolean tryAcquire(int acquires) {
            assert acquires == 1; // Otherwise unused
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int releases) {
            assert releases == 1; // Otherwise unused
            if (getState() == 0) throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
    }

    private final Sync sync = new Sync();

    @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 timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

    @Override
    public void unlock() {
        sync.release(1);
    }

    @Override
    public Condition newCondition() {
        return null;
    }


}
//Test Demo
public class TestDemo1 {
    static int count = 0;
    static Mutex mutex = new Mutex();

    public static void main(String[] args) throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(1000);

        for (int i = 0; i < 1000; i++) {

            new Thread(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    incrCount(countDownLatch);
                }
            }).start();
        }
        countDownLatch.await();
        System.out.println(count);

    }

    private static void incrCount(CountDownLatch countDownLatch) throws InterruptedException {
        //mutex.lock();
        Thread.sleep(10);
        System.out.println(count);
        count++;
        //mutex.unlock();
        countDownLatch.countDown();

    }
}

AQS 原理

AQS 方法初识

方法名称 方法描述 是否必须重写
final void acquire(int arg) 独占式获取同步状态,如果当前线程获取同步状态成功,该方法返回,否则,将会进入同步等待队列,改方法将会调用重写的tryAcquire方法 不能重写
boolean tryAcquire(int arg) 独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再cas设置同步状态 必须重写
final boolean release(int arg) 独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒,该方法将会调用重写的tryRelease方法 不能重写
boolean tryRelease(int arg) 独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态 必须重写

acquire获取锁

// 入口
public final void acquire(int arg) {
  // tryAcquire 失败后加入等待队列
  if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    //如果线程在排队过程中有其他线程对其进行了中断,则此处将中断标志位复位
    // 疑问1 :此处有何用  -》  parkAndCheckInterrupt方法配合
    selfInterrupt();
}


/**
    需要自己实现的
**/
@Override
protected boolean tryAcquire(int acquires) {
  assert acquires == 1; // Otherwise unused
  //cas 设置状态如果成功,获取锁成功,否则失败
  if (compareAndSetState(0, 1)) {
    setExclusiveOwnerThread(Thread.currentThread());
    return true;
  }
  return false;
}

// 向队列尾部增加一个节点
private Node addWaiter(Node mode) {
  Node node = new Node(Thread.currentThread(), mode);
  // Try the fast path of enq; backup to full enq on failure
  Node pred = tail;
  //获取尾节点,如果尾节点不为null,通过cas将当前节点替换为尾节点,返回当前节点
  if (pred != null) {
    node.prev = pred;
    //通过cas将当前节点设置为尾节点
    if (compareAndSetTail(pred, node)) {
      //将尾节点的nex指针指向当前节点
      pred.next = node;
      return node;
    }
  }
  // 如果尾节点为null
  enq(node);
  //返回当前节点
  return node;
}


 private Node enq(final Node node) {
        for (;;) {
          //如果尾节点为null,则说明队列未被初始化
          //通过cas将头节点和尾节点同时初始化未一个空节点
          //循环继续,此时t!=null,走else
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
              // 将当前节点的前一个节点设置为尾节点,
              // 通过cas将当前节点设置为尾节点
              // 将尾节点(等于头节点)的next 设置为当前节点
              // 此时当前队列中有两个节点,空的头节点,尾节点既当前节点
                // 退出当前循环
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

// 将当前节点增加到队列中
final boolean acquireQueued(final Node node, int arg) {
  boolean failed = true;
  try {
    // 初始化中断状态为否
    boolean interrupted = false;
    for (;;) {
      // 获取前一个节点
      final Node p = node.predecessor();
      // 如果前一个节点是头节点,尝试获取锁
      // 如果获取锁成功,将当前节点设置为头节点(cas),将之前的头节点设置为null
      // 以下设计要点,需要和release 方法一起看
      // 此时如果第一次没有获取锁,则shouldParkAfterFailedAcquire方法返回false,将头节点设置为-1,继续 下一次循环
      // 如果第二次循环依旧没有获取锁(非公平锁,被别的给抢了),那么shouldParkAfterFailedAcquire返回true。线程park。直到其他线程执行完release锁的时候,依旧会唤醒头节点的下一个节点。
      // 这种设计方式,关键点在于release永远是头节点的下一个节点,不管当前执行的是否为头节点的线程
      if (p == head && tryAcquire(arg)) {
        setHead(node);
        p.next = null; // help GC
        failed = false;
        return interrupted;
      }
      // 如果前一个节点不是头节点或者获取锁失败
      // shouldParkAfterFailedAcquire 方法是将前一个节点的waitStatus设置为-1才会返回true
      // 如果shouldParkAfterFailedAcquire 返回false,则会一直循环直到返回true,
      // 然后执行parkAndCheckInterrupt方法,转到parkAndCheckInterrupt方法
      if (shouldParkAfterFailedAcquire(p, node) &&
          parkAndCheckInterrupt())
        interrupted = true;
    }
  } finally {
    if (failed)
      cancelAcquire(node);
  }
}

// 该方法目的为设置前一个节点的waitStatus 设置为-1
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
  int ws = pred.waitStatus;
  //如果前一个节点状态为-1,返回true
  if (ws == Node.SIGNAL)
    /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
    return true;
  // 如果前一个节点状态大于0,即1为取消状态,则需要向前寻找,一直找到状态不大于0的节点 realPre
  // 将当前节点的pre 指针指向 realPre
  if (ws > 0) {
    /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
    // 只要pre节点状态大于0,即pre指针向前挪。node.pre 指向该节点
    do {
      node.prev = pred = pred.prev;
    } while (pred.waitStatus > 0);
    // 将状态不大于0的节点的next指针指向node 节点
    //退出该方法后,该方法返回false,继续循环
    pred.next = node;
  } else {
    // waitStatus此处只有俩个值的可能:0,-3
    // 0:初始值
    // -3: waitStatus值,指示下一个acquireShared应该无条件传播
           /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
    // waitStatus 是0或-3,表明我们需要一个信号,但是还没有park,
    // 调用方需要重试确定在park之前它确实不能获得锁
    // 如果此处成功或失败 继续循环
    compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
  }
  return false;
}

// park当前线程
// 如果当前线程为非阻塞状态,则方法执行到LockSupport.park 阻塞住,不会继续执行
// 如果当前线程为阻塞状态,则方法执行到LockSupport.park会立即返回,执行Thread.interrupted()将会将线程的中断状态设置为否,然后返回true,表示该线程是中断线程。
//下次循环LockSupport.park 会阻塞住非中断线程
// 当有LockSupport.unpark唤醒时,继续执行 Thread.interrupted。
// a。如果此时线程状态为未中断,parkAndCheckInterrupt返回false
    // acquireQueued 继续循环,尝试获得锁
    // 如果此时成功,acquireQueued返回false,线程非中断状态
    // 如果此时失败,shouldParkAfterFailedAcquire返回true,调用parkAndCheckInterrupt线程继续阻塞
// b。如果此时线程因为在等待过程中状态被设置为了中断状态,parkAndCheckInterrupt返回true
    // acquireQueued 继续循环,尝试获得锁
  // 如果此时成功,acquireQueued返回true,线程中断状态,执行acquire selfInterrupt()方法将线程置为中断 状态;
    //如果此时失败,shouldParkAfterFailedAcquire返回true,调用parkAndCheckInterrupt方法,因为当前线程为中断状态 LockSupport.park(this)会立即返回不会阻塞住该线程,此时调用Thread.interrupted()将中断状态转为非中断状态,parkAndCheckInterrupt返回true
    //acquireQueued.interrupted=true,acquireQueued继续循环
        //获取锁成功,则返回true,acquire selfInterrupt()设置当前线程中断状态
        //获取锁失败,shouldParkAfterFailedAcquire返回true,调用parkAndCheckInterrupt方法,因为此时线程已经市非中断状态,所以线程阻塞。
        
 private final boolean parkAndCheckInterrupt() {
   // LockSupport.park 
   // 如果当前线程为非中断状态,则线程阻塞到这里
   // 如果线程状态为中断状态,方法立刻返回(因为这个所以必须有下边的interrupted,配合使用)
        LockSupport.park(this);
   // 如果当前线程为中断状态,返回true,将线程至为 非中断状态
   // 如果当前线程为非中断状态,返回false
        return Thread.interrupted();
    }

如果parkAndCheckInterrupt 方法不用Thread.interrupted,用的是Thread.isInterrupted,就没有了将中断线程设置为非中断线程的功能,则LockSupport.park(this) 一直不能阻塞当前中断线程,导致acquireQueued会一直循环下去,耗费cpu。

release释放锁

// 入口
public final boolean release(int arg) {
  if (tryRelease(arg)) {
    // 锁释放成功后,获取头节点,如果头节点存在且waitstatus 不是初始化状态,说明后边有节点在等待
    // 唤醒的永远是头节点,
    // 即使是非公平锁,一上来就抢到了锁,当它执行完释放的时候也是释放之前的那个头节点之后的节点
    // 避免队列中的节点永远都拿不到锁
    Node h = head;
    if (h != null && h.waitStatus != 0)
      //唤醒头节点后边的节点
      unparkSuccessor(h);
    return true;
  }
  return false;
}

/**
    * 需要自己实现的
    * 释放锁不需要cas原因:
    * 不管是否可重入锁,出现问题的是多个线程之间操作,可重入锁也是同一个线程获取锁,一个线程执行程序是有序的
    * 在入口处 已经控制了只有一个线程可以获得锁,所以同一时刻释放锁的时候,有且只有一个线程会被执行。
**/
@Override
protected boolean tryRelease(int releases) {
  assert releases == 1; // Otherwise unused
  if (getState() == 0) throw new IllegalMonitorStateException();
  // 判断如果不是当前线程不是拥有锁的线程 拦截
  if (Thread.currentThread() != getExclusiveOwnerThread())
    throw new IllegalMonitorStateException();
  setExclusiveOwnerThread(null);
  // 为什么这里不需要cas?如果是可重入锁,这里会出现问题么?
  setState(0);
  return true;
}

/**
 * 唤醒h后的一个有效节点
**/
private void unparkSuccessor(Node node) {
  /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */     
  // 如果当前节点的状态为小于0 将当前节点状态设置为0初始值
  int ws = node.waitStatus;
  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.
         */
  // 获取当前节点的下一个节点s
  // 如果下一个节点s是null,或者状态waitStatus>0 (取消)不能被唤醒,将s初始化为null
  Node s = node.next;
  if (s == null || s.waitStatus > 0) {
    s = null;
    // t从队尾向前寻找;只要节点状态小于0,s指针一直向前移动;直到 t等于当前节点
    // 则s是离当前节点最近的一个状态小于等于0的下一个节点
    // 代码解析
        // t初始化为尾节点,每一次循环后如果t不等于当前节点, t = t.prev,t向前移动一个节点
        // 循环内容:如果t的status 小于等于0,等于说明后边没有节点在等待,当时当前节点需要被唤醒
        // s 等于当前t
        // 整个循环结束的条件是 t等于当前节点,此时 s等于上一个状态小于等于0的t
        // 说明s是下一个离当前节点最近的状态小于等于0的节点
    for (Node t = tail; t != null && t != node; t = t.prev)
      if (t.waitStatus <= 0)
        s = t;
  }
  // 找到下一个状态小于等于0的节点,需要被唤醒
  if (s != null)
    // 唤醒后 s.thread 继续在上锁的地方 LockSupport.park(s.thread)向下继续执行
    LockSupport.unpark(s.thread);
}

private static final boolean compareAndSetWaitStatus(Node node,
                                                     int expect,
                                                     int update) {
  //unsafe native方法 c实现的,不深究
  return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update);
}

AQS的扩展

ReentrantLock

支持一个线程多次获取锁,多次释放锁之后,其他线程才能拿到锁

公平锁 FairSync

获取锁的顺序是请求的绝对时间,也就是FIFO 
ReentrantLock
  static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    //获取锁入口
    final void lock() {
      acquire(1);
    }

    /**
     * 重写方法
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    protected final boolean tryAcquire(int acquires) {
      final Thread current = Thread.currentThread();
      // 获得锁的状态 
      int c = getState();
      if (c == 0) {
        // 如果锁未被锁定,判断队列中是否有等待线程 hasQueuedPredecessors
        // 如果没有,则尝试获取锁
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
          //将当前执行线程放入到锁中
          setExclusiveOwnerThread(current);
          return true;
        }
      }
      // 如果当前锁是被锁定状态
      // 判断当前线程与拥有锁的线程是否为同一个线程
      // 如果是则将锁的状态+1
      else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
          throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
      }
      return false;
    }
  }

AbstractQueuedSynchronizer
  /**
   * 如果队列中头节点后有其他等待线程则返回true
   * 如果队列中中没有等待数据或者没有其他等待节点则返回false
  **/
  public final boolean hasQueuedPredecessors() {
  // 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());
}

// 释放锁入口(公平与非公平实现逻辑相同)
public void unlock() {
  sync.release(1);
}
/**
    * AbstractQueuedSynchronizer 之前分析过了
**/
 public final boolean release(int arg) {
   // 全部释放后才会唤醒下一个节点
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

/**
*   重写方法
*/
protected final boolean tryRelease(int releases) {
  // 状态减去当前释放的数字
  int c = getState() - releases;
  // 判断必须当前线程调用
  if (Thread.currentThread() != getExclusiveOwnerThread())
    throw new IllegalMonitorStateException();
  // 锁是否全部释放 默认为否
  boolean free = false;
  // 如果状态为0 说明所有锁都释放了
  if (c == 0) {
    // 将当前线程置为null
    free = true;
    setExclusiveOwnerThread(null);
  }
  // 设置状态
  setState(c);
  // 如果全部释放 返回true,则release方法会唤醒头节点的下一个节点
  // 如果返回false 不会唤醒下一个节点
  return free;
}

非公平锁

非公平锁 并不是所有抢锁的线程一直争锁。根据代码发现,只是在有新的线程获取锁的时候,不会先排队,而是直接抢锁,如果此时抢到了,则执行顺序就排在了在队列中排队的线程,打破了FIFO,如果没有抢过还是进入对列排队,队列中的线程依旧是有序的执行FIFO。
/**
 * 非公平锁
 */
ReentrantLock
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
      //与公平锁的区别是,不管三七二十一先上来去抢锁
      //假如此时恰好有一个线程释放了锁,唤醒了下一个线程。
      // 但是下一个线程还没获得到锁的时候,当前线程就有可能获得锁
      // 那么被唤醒的线程循环两次后会继续被park
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            //走正常逻辑,加入队列尾部
            acquire(1);
    }
  
  /**
   * AbstractQueuedSynchronizer 之前分析过了
  **/
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
    final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //如果当前锁状态未锁定,再次尝试抢锁
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
         // 如果当前锁状态为锁定,但是持有锁的线程就是当前线程,则重入
         // 如果锁定且不等于当前线程,则尝试获取锁失败,加入队列尾部
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
}

OTHER

interrupt ,interrupted,isInterrupted

interrupt

public class TestThreadInterrupted {

    public static void main(String[] args) {
        //testInterrupt();
        testInterrupted();
    }

    /**
     * interrupt将线程设置为中断状态,但是不影响线程继续执行
     */
    private static void testInterrupt() {
        Thread.currentThread().interrupt();
        System.out.println(Thread.currentThread().isInterrupted());
        for (int i = 0; i < 10; i++) {
            System.out.println("testInterrupt:" + i);
        }
        System.out.println(Thread.currentThread().isInterrupted());

        if (Thread.currentThread().isInterrupted()) {
            throw new RuntimeException("相应中断状态");
        }

        System.out.println("end!!!");
    }
            /**
        * 如果当前线程状态为中断状态
        * 第一次调用Thread.interrupted(),此时方法返回true,表示线程当前状态为中断状态,且该方法将线程状态设置为非中断状态,即归位。
        * 第二次调用Thread.interrupted(),此时方法放回false,表示当前线程非中断状态,且不做状态转换
        * 如果当前线程状态为非中断状态
        * 第几次调用都是false,且不会改变当前线程状态
      **/
    private static void testInterrupted() {
        //Thread.currentThread().interrupt();
        
        System.out.println(Thread.currentThread().isInterrupted());
        for (int i = 0; i < 10; i++) {
            System.out.println("testInterrupted:" + i);
        }
        System.out.println("第一次:" + Thread.interrupted());
        System.out.println("第一次调用之后状态" + Thread.currentThread().isInterrupted());

        System.out.println("第二次:" + Thread.interrupted());
        System.out.println("第二次调用之后状态" + Thread.currentThread().isInterrupted());
        if (Thread.currentThread().isInterrupted()) {
            throw new RuntimeException("相应中断状态");
        }

        System.out.println("end!!!");
    }
}

注意:如果当前线程在park阶段,其他线程将park线程改为中断状态,则阻塞线程被唤醒,不再继续阻塞

你可能感兴趣的:(AQS)