java基础ReentrantLock和AQS同步队列

ReentrantLock

在这里插入图片描述
lock有这几个方法,
在这里插入图片描述
这些类实现的lock。reentrantReadWriteLock属于共享锁,reentrantLock属于互斥锁
在这里插入图片描述
下面我们重点分析ReentrantLock.

同步队列(AQS)

同步队列,全称为:AbstractQueuedSynchronizer,AQS依赖内部的一个FIFO双向队列来完成同步状态的管理。在Lock中,这是一个非常重要的核心组件,J.U.C工具包中很多地方都使用到了AQS,所以,如果理解了AQS,那么再去理解ReentrantLock,Condition,CountDownLatch等工具的实现原理,就会非常轻松。
在这里插入图片描述

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;//表示当前线程正在等待锁,需要中断
        static final int CONDITION = -2;//Condition队列有使用到,暂时用不到
        static final int PROPAGATE = -3;//CountDownLatch等工具中使用到,暂时用不到
        volatile int waitStatus;//节点中线程的状态,默认为0
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        Node nextWaiter;
        ...
    }

ReentrantLock#lock()

   public void lock() {
        sync.lock();
    }

我们发现这里调用了sync中的一个lock()方法,sync是ReentrantLock类当中的一个组合抽象类,我们知道,AQS是一个同步队列,但是因为AQS只是一个同步队列,并不具备一些业务执行能力,所以通过了另一个Sync来继承AbstractQueuedSynchronizer,并根据不同的业务场景来实现不同的业务逻辑。sync有两个实现类NonfairSync和FairSync,也就是公平锁和非公平锁。

NonfairSync#lock()

private transient Thread exclusiveOwnerThread;
		final void lock() {
			//cas设置state=1
            if (compareAndSetState(0, 1))
            	//成功的话 保存当前线程为AQS的OwnerThread
                setExclusiveOwnerThread(Thread.currentThread());
            else
            //失败则插入aqs
                acquire(1);
        }
AQS#compareAndSetState
protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

上面代码的意思是,如果当前内存中的stateOffset的值和预期值expect相等,则替换为 update值。更新成功返回true,否则返回false。这个操作是原子的,不会出现线程安全问题。
stateOffset是AQS中的一个属性,它在不同的实现中所表达的含义不一样,对于重入锁的实现来说,表示一个同步状态。它有两个含义:
1.当 state=0 时,表示无锁状态
2.当 state>0 时,表示已经有线程获得了锁,也就是state=1,但是因为ReentrantLock允许重入,所以同一个线程多次获得同步锁的时候,state会递增,比如重入 5 次,那么state=5。而在释放锁的时候,同样需要释放5次直到state=0其他线程才有资格获得锁。
目前AQS队列中并没有节点,只是设置了exclusiveOwnerThread。

AQS#acquire
public final void acquire(int arg) {    
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))       
          selfInterrupt();
    }
Sync#nonfairTryAcquire 非公平锁
	 protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
     final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            //0时,表示无锁状态,当 state>0 时,表示已经有线程获得了锁
            int c = getState();
            if (c == 0) {
            	//无锁时cas设置锁
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //有锁,但current等于owner说明是重入锁,增加state
            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;
        }
Sync#fairTryAcquire 公平锁
protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
         ...
        }

公平不公平主要体现在tryAcquire方法,公平锁多了一个hasQueuedPredecessors()判断。下面举两个例子
非公平锁
持有锁的线程A正在running,队列中有线程BCD被挂起并等待被唤醒;
在某一个时间点,线程A执行unlock,唤醒线程B;
同时线程E执行lock,这个时候会发生什么?线程B和E拥有相同的优先级,这里讲的优先级是指获取锁的优先级,同时执行CAS指令竞争锁。如果恰好线程E成功了,线程B就得重新挂起等待被唤醒。
公平锁
E执行lock时通过hasQueuedPredecessors()方法发现不是头结点,则不能cas去获取锁。要加到队尾。

AQS#addWaiter(Node)

走到这说明没获取到锁,所以当前线程会被初始化成为一个Node节点。

/**
     * Creates and enqueues node for current thread and given mode.
     *互斥锁 or 共享锁
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new 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; 
        //第一次进来是队列还没构建,所以tail一定为null,不进if
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
    
	private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // tail为null说明要初始化队列
                if (compareAndSetHead(new Node())) //初始化head
                    tail = head; //初始化tail 
            } else {
                node.prev = t; //新节点放队尾,所以原来的tail变为倒数第二个节点
                if (compareAndSetTail(t, node)) {//设置当前节点为tail
                    t.next = node;//所以原来的tail变为倒数第二个节点,所以next指向当前节点
                    return t;
                }
            }
        }
    }
    
   private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }

    /**
     * CAS tail field. Used only by enq.
     */
    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

enq第一次循环
enq第一次循环
enq第二次循环
在这里插入图片描述

AQS#acquireQueued

经过addWaiter后已经把Node加到AQS中了,但线程并没有被挂起。所以acquireQueued(Node,arg)方法中会做的两件事:
1、看看前一个节点是不是头节点,如果是的话,就再试一次
2、再试一次如果还是失败了,那么线程正式挂起

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; // 移除原来的head
                    failed = false;
                    return interrupted;
                }
                //还是加锁失败,线程将被挂起
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
         /*
         *    static final int CANCELLED =  1;//表示当前线程状态是取消的,需要移除队列
        static final int SIGNAL    = -1;//表示当前线程正在等待锁,需要中断
        static final int CONDITION = -2;//Condition队列有使用到,暂时用不到
        static final int PROPAGATE = -3;//CountDownLatch等工具中使用到,暂时用不到
        volatile int waitStatus;//节点中线程的状态,默认为0
         */
        if (ws == Node.SIGNAL)
            // 前一节点已经阻塞,前期节点可直接阻塞
            return true;
        if (ws > 0) { //ws=1
            /*
             * 前一节点是取消状态我们要移除取消的节点
             */
            do {
                node.prev = pred = pred.prev; //prev=prev.prev; node.prev=prev; 当前节点指向前前节点
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus =0或PROPAGATE。当前线程被挂起前,需要前节点状态是SINGLE,因为unlock时要唤醒SINGLE的节点去争抢锁
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

因为我们初始化时head里放的是 new Node() waitStatus=0,所以第一次会执行shouldParkAfterFailedAcquire#compareAndSetWaitStatus,将waitStatus设置成-1。第二次循环会走ws == Node.SIGNAL并且shouldParkAfterFailedAcquire返回true,下面继续看parkAndCheckInterrupt()

parkAndCheckInterrupt
  private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this); //线程走到这将被挂起,下面的return不会执行
        return Thread.interrupted();
    }

至此完成了lock()操作。

ReentrantLock#unlock()

释放锁时sync没有fair和nofair

  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);
            return true;
        }
        return false;
    }
  protected final boolean tryRelease(int releases) {
            int c = getState() - releases; //获取state-释放的次数
            //当前线程才能释放锁
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //c等于0说明当前线程重入的锁都释放了,目前线程没有持有锁
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

如果tryRelease返回true,AQS的结构如下
在这里插入图片描述

unparkSuccessor
private void unparkSuccessor(Node node) {
        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.
         */
        Node s = node.next;
        如果下一个节点为空或者被取消了
        if (s == null || s.waitStatus > 0) {
            s = null;
            //从尾部找到状态小于等于0的有效节点
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;//注意没有break
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

从尾部遍历找到有效节点后,unpark唤醒线程。

释放锁后AQS#acquireQueued
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; // 移除原来的head
                    failed = false;
                    return interrupted;
                }
                //还是加锁失败,线程将被挂起
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

加锁时我们是在parkAndCheckInterrupt()中阻塞的,释放锁后则继续进行循环,会走到“if (p == head && tryAcquire(arg))”这个判断,由于目前state=0,所以节点中的线程设置state=1,设置exclusiveOwnerThread。

 private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }

head中thread=null只因为存到了exclusiveOwnerThread中。

selfInterrupt
 public final void acquire(int arg) {
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
     static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

根据acquireQueued中interrupted 的状态看是否执行selfInterrupt()

doAcquireInterruptibly

lock.lockInterruptibly() 中parkAndCheckInterrupt()中断后会抛异常,然后执行cancelAcquire()

 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())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
cancelAcquire
private void cancelAcquire(Node node) {
    // Ignore if node doesn't exist
    if (node == null)
        return;
    //1. node不再关联到任何线程
    node.thread = null;
    //2. 跳过被cancel的前节点,找到一个有效的前节点pred
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;
    //删除了前面取消的节点    
    Node predNext = pred.next;
    //3. 由于自己也被中断了,所以将node的waitStatus置为CANCELLED
    node.waitStatus = Node.CANCELLED;
    //4. 如果node是tail,更新tail为pred,并使pred.next指向null
    if (node == tail && compareAndSetTail(node, pred)) {
        compareAndSetNext(pred, predNext, null);
    } else {
        int ws;
        //5. 如果node既不是tail,又不是head的后继节点
        //则将node的前继节点的waitStatus置为SIGNAL
        //并使node的前继节点指向node的后继节点
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) {
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
        //6. 当前节点的前置节点是head节点,那就直接把下一个节点唤醒
            unparkSuccessor(node);
        }
        node.next = node; // help GC
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值