三、AQS框架详解——AbstractQueuedSynchronizer

一、JMM模型与volatile详解
二、synchronized原理详解
三、AQS框架详解——AbstractQueuedSynchronizer
四、ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue和DelayQueue学习总结
五、CountDownLatch、CyclicBarrier和Semaphore的比较
java中interruptor理解与使用
Java线程的6种状态及切换
MESI协议:保证可见性,无法保证原子性
AQS定义了一套多线程访问共享资源的同步框架,一个依赖状态(state)的同步器。

ReentrantLock

lock.lock()
//业务逻辑
lock.unLock()

下面是模拟三个线程执行上面代码的简单流程图:
在这里插入图片描述

从上面lock的用法中,我们总结出三个核心问题:
(1)自旋;
(2)阻塞线程,并放入队列中;同步等待队列(CLH队列);
(3)如何实现加锁;CAS类实现加锁,底层是汇编指令CMPXCHG

ReentranLock是基于AQS框架实现的,支持手动加锁与解锁。支持锁的公平与非公平。

static ReentrantLock lock = new ReentrantLock(false);
public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

ReentrantLock通过两个继承Sync的内部类实现,FairSync,NonfairSync来公平锁和非公平锁。

static final class FairSync extends Sync 
static final class NonfairSync extends Sync
abstract static class Sync extends AbstractQueuedSynchronizer

而Sync类就是继承自AbstractQueuedSynchronizer。

FairSync.lock()源码分析

在这里插入图片描述

tryAcquire()方法—锁竞争逻辑

protected final boolean tryAcquire(int acquires) {
            //获取当前线程的引用
            final Thread current = Thread.currentThread();
            //获取当前线程的状态量
            int c = getState();
            //如果状态量等于0,表示是可以加锁的状态;
            if (c == 0) {
            //hasQueuedPredecessors():判断是否需要排队;
            //compareAndSetState(0, acquires):通过CAS的方式修改状态量,加锁
            //setExclusiveOwnerThread(current):把OwnerThread标记为当前线程;
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果状态量不为0,并且OwnerThread就是当前线程,重入逻辑;否则,加锁失败
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

compareAndSetState()

通过unsafe类,利用CAS操作,完成加锁操作;
底层依赖汇编指令CMPXCHG

protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

addwaiter(Node.EXCLUSIVE) —线程入队,Node节点,Node对Thread引用

node节点示意图
在这里插入图片描述

 // mode是Node.EXCLUSIVE;初始化一个node节点;
    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.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //队列为空,初始化一个队列,并且放入队列中;
        //利用for循环保证入队成功
        enq(node);
        return node;
    }

acquireQueued

 final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            //中断标志开始为false;
            boolean interrupted = false;
            //第一次循环,修改head的状态,修改成signal=-1标记为可以唤醒;
            //第二轮循环,阻塞线程,并设置中断为true;
            for (;;) {
            //如果一开始是第一个节点,就再尝试一次获取锁
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    //中断标志为false;
                    return interrupted;
                }
                //如果不是第一个节点,则需要阻塞并且中断标志设置为true;
                //shouldParkAfterFailedAcquire,判断是否需要阻塞线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

唤醒方式:unlock唤醒队列头部信息;
中断方式也可以唤醒:thread.interrupt()

场景:
Interrupt存在的意义,终止线程,避免影响重要的业务;
提供一个终止线程的方法,优雅的终结方式;

unlock()源码分析

在这里插入图片描述

release(int arg)

   //释放锁的方法
    public final boolean release(int arg) {
       //尝试释放锁,释放成功的话,判断waitStatus!=0,则调用unpark方法,唤醒线程
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
            //该方法会判断线程状态是不是!=0;
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryRelease(int releases):释放锁的逻辑

//释放锁的逻辑
protected final boolean tryRelease(int releases) {

    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

AQS具备的特性

1、阻塞等待队列
2、共享、独占
3、公平、非公平
4、可重入
5、允许中断

AQS的重要属性state,同步状态量

在这里插入图片描述
state=0,表示锁还没有被占有;state不等于0时,thread表示的持有锁的线程,重入一次,state自增1。

 /**
     * The synchronization state.
     */
    private volatile int state;

CAS的方式改变状态量的值:

protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

独占/共享

  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;

EXCLUSIVE:独占,只有一个线程能执行,如ReentrantLock;
SHARED:共享,多个线程可以同时执行,比如Semaphore/CountDownLatch。

同步等待队列和条件等待队列

AQS中同步等待队列也叫CLH队列,是一种双向链表的结构,先进先出的线程等待队列。

在这里插入图片描述
condition是多线程间协调通信的工具类,线程只有当条件具备时,才会被唤醒,从而重新进行锁的争夺。
在这里插入图片描述

AQS源码分析

不管是CLH队列还是条件队列都是基于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;
        /** 节点在等待队列中,节点的线程等待在condition上,当其他线程对condition调用了sinal()方法后,该节点会从等待队列转移到CLH队列中,加入同步状态的获取中*/
        static final int CONDITION = -2;
        /**表示下一次共享式同步状态获取将会被无条件地传播下去
         */
        static final int PROPAGATE = -3;

节点加入同步队列

/*节点加入同步队列*/
private Node enq(final Node node) {
        for (;;) { //自旋等待
            Node t = tail;
            if (t == null) { // 队列需要初始化,创建空的头节点
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

尝试获取共享锁

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);
                    //state等于0说明共享次数达到了,可以获取锁了
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

头节点在节点阻塞之前再尝试一次获取锁:成功,出队,执行,并且head后移;阻塞:(1)首先第一轮循环修改head的状态signal为可以被唤醒;(2)第二轮循环,返回true,阻塞线程并且需要判断线程是否由中断信号唤醒的;(3)

参考文献:
从ReentrantLock的实现看AQS的原理及应用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值