AQS

AbstractQueuedSynchronizer(AQS)

1.可重入锁(递归锁)

1.1 理论

概念:同一个线程在外层方法获得锁得时候,再进入该线程内层方法会自动获取锁(同一把锁);
举例:ReentrantLock,Synchronized都是可重入锁
优点:一定程度上避免死锁

可:可以
重:再次
入:进入
锁:同步锁
--一个线程中得多个流程可以获取同一把锁,持有这把同步锁可以再次进入;
自己获取自己的内部锁

-种类:
1)隐式锁(Synchronized默认可重入锁)
2)显示锁(Lock)有ReentraintLock

1.2 代码
  • 隐式锁:分别用同步代码块和同步方法实验
public class ReEnterLockDemo {
    static Object objectLockA = new Object();

    public static void m1(){
        new Thread(() -> {
            synchronized (objectLockA){
                System.out.println(Thread.currentThread().getName() + "\t" + "--外层调用");
                synchronized (objectLockA){
                    System.out.println(Thread.currentThread().getName() + "\t" + "--中层调用");
                    synchronized (objectLockA){
                        System.out.println(Thread.currentThread().getName() + "\t" + "--内层调用");
                    }
                }
            }
        }, "t1").start();
    }

    public static void main(String[] args) {
        m1();
//        可重入性,不阻塞直接输出
//        t1    --外层调用
//        t1    --中层调用
//        t1    --内层调用
    }
}
public class ReEnterLockDemo2 {
    public static synchronized void m1(){
        System.out.println("外层");
        m2();
    }

    public static synchronized void m2(){
        System.out.println("中层");
        m3();
    }

    public static synchronized void m3(){
        System.out.println("内层");
    }

    public static void main(String[] args) {
        m1();
        //同步方法也同样没有阻塞
//        外层
//        中层
//        内层
    }

2.LockSupport

  • 来自java.utils.concurrent.locks.LockSupport:用于创建锁和其他同步类得基本线程阻塞原语(线程等待唤醒机制wait/notify得改良加强版park()/unpark());
2.1 wait/notify得缺陷
  • 三种等待唤醒方法
    1)使用Object中的wait方法等待,使用Object中的notify方法唤醒线程
    2)使用JUC包中的Condition得await方法等待,signal唤醒线程
    3)LockSupport类可以阻塞当前线程以及唤醒指定被阻塞得线程;
    过去得等待唤醒机制.png
  • 展开synchronized
public class LockSupportDemo1 {

    static Object objectLock = new Object();
    
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (objectLock){
                System.out.println(Thread.currentThread().getName() + "\t" + "--comein");
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒");
            }
        }, "A").start();

        new Thread(() -> {
            synchronized (objectLock){
                objectLock.notify();
                System.out.println(Thread.currentThread().getName() + "\t" + "--通知notify");
            }
        }, "B").start();

//        A --comein
//        B --通知notify
//        A 被唤醒
    }
}
  • 缺点:
    1)不能脱离synchronized,否则报java.lang.IllegalMonitorStateException
    2)wait/notify执行顺序会影响最终输出,正常要先wait再notify;
  • 展开Lock
public class LockSupportDemo2 {
    static Lock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static void main(String[] args) {
        new Thread(() -> {
            lock.lock();
            try{
                System.out.println(Thread.currentThread().getName() + "\t" + "..comein");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒");
            }finally {
                lock.unlock();
            }
        }, "A").start();

        new Thread(() -> {
            lock.lock();
            try{
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "\t" + "执行signal");
            }finally {
                lock.unlock();
            }
        }, "B").start();

//        A ..comein
//        B 执行signal
//        A 被唤醒
    }
}
  • 缺点:
    1)不能脱离Lock,否则报java.lang.IllegalMonitorStateException
    2)await/signal执行顺序会影响最终输出,正常要先await再signal;
  • 总结
    1)线程先要获得锁,并且再锁块(synchronized和lock)中
    2)必须要先等待后唤醒
2.2 LockSupport展开
  • 理论:通过park和unpark方法执行阻塞和唤醒线程;
    提供了一个Permit许可(0,1,且不许累加,最大为1,默认0)
  • 底层是UNSAFE类
public static void park() {
        UNSAFE.park(false, 0L);
    }

public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }
  • demo
public class LockSupportDemo3 {
    public static void main(String[] args) {
        Thread a = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t" + "..comein");
            LockSupport.park();//需要许可证
            System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒");

        }, "A");
        a.start();

        Thread b = new Thread(() -> {
            LockSupport.unpark(a);//对指定线程发放许可
            System.out.println(Thread.currentThread().getName() + "\t" + "执行signal");

        }, "B");
        b.start();


//        A ..comein
//        B 执行signal
//        A 被唤醒
  • 优势:
    1)不需要同步代码块或锁
    2)unpark可以再park之前执行,同样支持

  • 总结:
    1)LockSupport是一个线程阻塞工具类,所有方法都是static,可以让线程再任意位置阻塞,也有对应唤醒方法,底层调用得Unsafe中得native代码(底层原语)。
    2)LockSupport和每个使用它的线程都有一个许可permit关联(相当于0,1开关,默认为0);
    调用一次unpark就加1变为1
    调用一次park就消费permit,把1变为0,同时park立即返回;
    再次调用park会阻塞(因为permit为0会阻塞在这里,直到permit变为1),这时调用unpark会把permit置为1
    每个线程都有一个相关得permit,permit最多只有一个,重复调用unpark也不会积累

  • 多次调用unpark不累计permit

public static void main(String[] args) {
        Thread a = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t" + "..comein");
            LockSupport.park();//需要许可证
            LockSupport.park();//需要许可证
            System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒");

        }, "A");
        a.start();

        Thread b = new Thread(() -> {
            LockSupport.unpark(a);//对指定线程发放许可
            LockSupport.unpark(a);//对指定线程发放许可
            System.out.println(Thread.currentThread().getName() + "\t" + "执行signal");

        }, "B");
        b.start();

//        unpark只能置permit为1,更不上两个park得消耗
//        A ..comein
//          B   执行signal

线程阻塞需要消耗permit,而permit最多只有一个:

3 AQS

3.1 前置知识

ReentrantLock

java.util.concurrent.locks.AbstractQueuedSynchronizer:是一个抽象类、链表结构、

功能:构建锁或者其他同步器组件(抽取出公共功能组建抽象类):通过内置得FIFO队列完成资源获取线程得排队工作,并通过一个int类型变量表示持有锁得状态;

3.2 作用
  • ReentrantLock
  • CountDownLatch
  • ReentrantReadWriteLock
  • Semaphare
    都有一个内部类Sync(继承自AQS)
abstract static class Sync extends AbstractQueuedSynchronizer
  • 锁:面向锁得使用者,
    同步器:面向锁的实现者

    前者定义了程序和锁交互得使用层api,隐藏了实现细节,调用即可;
    后者:统一的规范

  • 作用:
    1)加锁会导致阻塞->排队->等待队列管理

3.3 AQS初步
  • 使用一个volatile得int成员变量来表示同步状态,通过内置得FIFO队列来完成资源获取得排队工作,将每条要去抢占得线程封装为一个Node节点来实现锁得分配,通过CAS完成对State值得修改
    实现类.png

    image.png
  • 状态state,volatile保证可见性,CAS修改保证原子性


    可见性.png

    自旋原子性.png
  • CLH双向队列


    入队在尾,出队在头.png
static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;

        /** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3;

        /**
         * Status field, taking on only the values:
         *   SIGNAL:     The successor of this node is (or will soon be)
         *               blocked (via park), so the current node must
         *               unpark its successor when it releases or
         *               cancels. To avoid races, acquire methods must
         *               first indicate they need a signal,
         *               then retry the atomic acquire, and then,
         *               on failure, block.
         *   CANCELLED:  This node is cancelled due to timeout or interrupt.
         *               Nodes never leave this state. In particular,
         *               a thread with cancelled node never again blocks.
         *   CONDITION:  This node is currently on a condition queue.
         *               It will not be used as a sync queue node
         *               until transferred, at which time the status
         *               will be set to 0. (Use of this value here has
         *               nothing to do with the other uses of the
         *               field, but simplifies mechanics.)
         *   PROPAGATE:  A releaseShared should be propagated to other
         *               nodes. This is set (for head node only) in
         *               doReleaseShared to ensure propagation
         *               continues, even if other operations have
         *               since intervened.
         *   0:          None of the above
         *
         * The values are arranged numerically to simplify use.
         * Non-negative values mean that a node doesn't need to
         * signal. So, most code doesn't need to check for particular
         * values, just for sign.
         *
         * The field is initialized to 0 for normal sync nodes, and
         * CONDITION for condition nodes.  It is modified using CAS
         * (or when possible, unconditional volatile writes).
         */
        volatile int waitStatus;

        /**
         * Link to predecessor node that current node/thread relies on
         * for checking waitStatus. Assigned during enqueuing, and nulled
         * out (for sake of GC) only upon dequeuing.  Also, upon
         * cancellation of a predecessor, we short-circuit while
         * finding a non-cancelled one, which will always exist
         * because the head node is never cancelled: A node becomes
         * head only as a result of successful acquire. A
         * cancelled thread never succeeds in acquiring, and a thread only
         * cancels itself, not any other node.
         */
        volatile Node prev;

        /**
         * Link to the successor node that the current node/thread
         * unparks upon release. Assigned during enqueuing, adjusted
         * when bypassing cancelled predecessors, and nulled out (for
         * sake of GC) when dequeued.  The enq operation does not
         * assign next field of a predecessor until after attachment,
         * so seeing a null next field does not necessarily mean that
         * node is at end of queue. However, if a next field appears
         * to be null, we can scan prev's from the tail to
         * double-check.  The next field of cancelled nodes is set to
         * point to the node itself instead of null, to make life
         * easier for isOnSyncQueue.
         */
        volatile Node next;

        /**
         * The thread that enqueued this node.  Initialized on
         * construction and nulled out after use.
         */
        volatile Thread thread;

        /**
         * Link to next node waiting on condition, or the special
         * value SHARED.  Because condition queues are accessed only
         * when holding in exclusive mode, we just need a simple
         * linked queue to hold nodes while they are waiting on
         * conditions. They are then transferred to the queue to
         * re-acquire. And because conditions can only be exclusive,
         * we save a field by using special value to indicate shared
         * mode.
         */
        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封装了Thread对象,并有prev和next作为

AQS源码

  • Lock接口得实现类,基本都是通过【聚合】一个【队列同步器】得子类完成线程访问控制得
    1.用户表面使用的接口(比如lock()unlock())在内部实际调用的是Sync内部类得方法
public void unlock() {
        sync.release(1);
    }
底层都是用的sync类得方法:
而Sync内部类是实现得AQS:
abstract static class Sync extends AbstractQueuedSynchronizer

2.定义公平还是非公平还是用的Sync子类

public ReentrantLock() {
        sync = new NonfairSync();
默认非公平
    }
 public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

而公平和非公平同步器继承自Sync(AQS实现类)
是Sync得子类
static final class FairSync extends Sync
static final class NonfairSync extends Sync
image.png
继承关系.png

二者得主要区别:


多了一个限制条件hasQueuedPredecessors(),公平锁加锁时候判断等待队列中是否存在有效节点得方法.png

因为公平锁讲究先来先到


image.png

判断当前线程是否是等待队列中的第一个节点,如果前面还有线程就返回true.png

(ctrl+alt+b查看实现类)

  • 区别:
    公平锁:先来先得,线程在获取锁时候,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入等待队列。
    非公平锁:不管是否有等待队列,只要可以获取锁,就立刻占有锁对象

  • 银行案例
    进入非公平锁的lock()

final void lock() {
            if (compareAndSetState(0, 1))
一个CAS的compareAndSwapInt,确保原子性。如果确实没人占用,就设为1
                setExclusiveOwnerThread(Thread.currentThread());
设置为当前线程
            else
                acquire(1);
如果state不为0,也就是被占用,调用acquire方法
        }
复习一下CAS.png

image.png
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

三个方法:
  • tryAcquire方法
AQS中的tryAcquire方法:
protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
典型的模板方法设计模式,实现类类必须实现该方法,否则抛出异常。
--------------------------------------------------------------------------------------------
NonfairSync:实现了这个方法
protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();//当前线程
            int c = getState();//得到state的值
            if (c == 0) {//而如果state为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;
        }
  • addWaiter方法
AQS中的第二个条件判断:
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
------------------------------------------------
AQS:
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;
        if (pred != null) {//如果队列中已经有node,加到tail后,不需要进enq方法
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);//放入cls队列,tail后;空队列会创建傀儡节点
        return node;
    }

//入队功能,头节点是一个傀儡节点
private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))//新建一个node作为头节点,不是B线程节点
                    tail = head;//一个null的空节点,并把头尾指针指向它
            } else {//当前节点插入到哨兵节点或tail节点后,并更新tail节点指向
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
  • 第三个方法:acquireQueued
AQS:
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();//node的前一个节点
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&//如果前驱节点的waitstatus是-1(SIGNAL)就返回true
                    parkAndCheckInterrupt())//LockSupport.park(this),线程被阻塞,真正入队
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

在这一步才真正park阻塞
private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
总结.png

  • 下面看unlock方法
public void unlock() {
        sync.release(1);
    }

public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;//头指针,傀儡节点
            if (h != null && h.waitStatus != 0)//也就是判断队列中是否存在阻塞的候选节点
                unparkSuccessor(h);//唤醒head.next一个阻塞节点,并且头节点waitStatus设回0,B继续执行
            return true;
        }
        return false;
    }

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;//state - 1
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);//且设置当前持有锁为null
            }
            setState(c);//重新设置state
            return free;
        }


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);//设置node为头节点,并把node的Thread设置为null,也就是说当前出队节点变成新的头傀儡节点
                    p.next = null; // help GC,帮助头节点出队
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())//unpark后可以继续执行下去,进入下一个内循环
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

你可能感兴趣的:(AQS)