并发学习记录19:ReentrantLock和AQS

概述

AQS全称是AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架

特点是:
用state属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁
state的访问方式有三种,如下:
getState方法是获取state状态
setState方法是设置state状态
compareAndSetState是利用cas机制设置state状态
独占模式是只有一个线程能够访问资源,而共享模式指的是允许多个线程访问资源

AQS提供了基于先进先出的等待队列,类似于Monitor的EntryList

AQS还提供了条件变量来实现等待、唤醒机制,支持多个条件变量,类似于Monitor中的WaitSet

子类主要需要实现这样的一些方法:

//尝试以独占方式去获得锁
tryAcquire
//尝试去释放锁
tryRelease
//尝试以共享模式去获取锁
tryAcquireShared
//尝试以共享模式去释放锁
tryReleaseShared
//判断当前线程是否是正在独占资源,conditionObject用到的时候就需要去实现它
//Returns:true if synchronization is held exclusively; false otherwise
//This method is invoked internally only within AbstractQueuedSynchronizer.ConditionObject methods
isHeldExclusively

AQS的节点状态:

        /** 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;

一共有以上四种,节点状态用waitStatus记录:
*CANCELLED :表示这个节点已经被取消调度,当timeout或被中断时会出发变成这个状态,进入这个状态的节点不会再变化。
*SIGNAL :表示后继的节点需要被unparking。后继节点入队时,前驱节点会更新成为signal

  • CONDITION :表示这个节点上的线程等待在condition上,当其他线程调用了condition的signal方法,condition状态的节点将从等待队列转移到同步队列中,等待获取同步锁
  • PROPAGATE :共享模式下,前驱节点不止会唤醒后继节点,可能也会唤醒其他节点。

acquire(int)

/**
     * Acquires in exclusive mode, ignoring interrupts.  Implemented
     * by invoking at least once {@link #tryAcquire},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquire} until success.  This method can be used
     * to implement method {@link Lock#lock}.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquire} but is otherwise uninterpreted and
     *        can represent anything you like.
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

tryAcuqire(int)

这个方法尝试去获取独占资源,更加具体的获取方式就要由继承AQS的子类去实现。

    /**
     * Attempts to acquire in exclusive mode. This method should query
     * if the state of the object permits it to be acquired in the
     * exclusive mode, and if so to acquire it.
     *
     * <p>This method is always invoked by the thread performing
     * acquire.  If this method reports failure, the acquire method
     * may queue the thread, if it is not already queued, until it is
     * signalled by a release from some other thread. This can be used
     * to implement method {@link Lock#tryLock()}.
     *
     * <p>The default
     * implementation throws {@link UnsupportedOperationException}.
     *
     * @param arg the acquire argument. This value is always the one
     *        passed to an acquire method, or is the value saved on entry
     *        to a condition wait.  The value is otherwise uninterpreted
     *        and can represent anything you like.
     * @return {@code true} if successful. Upon success, this object has
     *         been acquired.
     * @throws IllegalMonitorStateException if acquiring would place this
     *         synchronizer in an illegal state. This exception must be
     *         thrown in a consistent fashion for synchronization to work
     *         correctly.
     * @throws UnsupportedOperationException if exclusive mode is not supported
     */
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

acquireQueued就是把当前线程放入等待队列,如果等待队列中只有当前线程在等待,当前线程就尝试去获取资源,如果等待队列有很多线程在等待,当前线程就进入阻塞状态。

    /**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @param node the node
     * @param arg the acquire argument
     * @return {@code true} if interrupted while waiting
     */
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
           	//自旋锁
            for (;;) {
            	//p是node的前驱
                final Node p = node.predecessor();
                //如果node的前驱是head节点,由于head节点内部不含线程,所以此时					//node就是等待队列的头节点,所以可以尝试去尝试获取资源
                if (p == head && tryAcquire(arg)) {
                	//这里node成功获取了资源
                 	//node变成head
                    setHead(node);
                    //node的前驱彻底出队
                    p.next = null; // help GC
                    //成功获得资源
                    failed = false;
					//返回等待过程中是否被打断
                    return interrupted;
                }
                //能执行到这里,说明node的前驱非头结点,或者node自己在竞争资源的时                 //候失败了
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node)

检查并更新当前这个没有获得资源的节点的状态,如果当前这个节点中的线程需要阻塞就返回true。
如果返回true,就说明前驱节点状态正常,当前线程需要被挂起,然后就park当前线程等待被唤醒
如果返回false,不挂起线程,而是重新进入自旋锁,由于走了一遍shouldParkAfterFailedAcquire的do-while循环,所以此时本线程的前驱已经是signal状态的前驱

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
		//获取前驱节点的状态
        int ws = pred.waitStatus;
        //如果前驱节点是状态为SIGNAL,说明node节点是应该被唤醒的,但是又没被唤醒,    		//所以node节点中的线程应该是需要被挂起的
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
        //如果前驱节点大于0,那么就说明前驱节点已经被cancel,而等待队列中节点的唤醒
        //都是依靠前驱节点来唤醒的,前驱节点状态为负数值才能唤醒后续节点,所以就得往
        //前遍历,直到找到前驱节点状态为负数的节点,然后把node的前驱指向这个找到的新		 //前驱
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * 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非cancel和signal,需要改成
            //signal来唤醒后续节点
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

addWaiter(Node)

这个方法就是将当前的线程放入等待队列的队尾,最后返回当前线程所在的节点。

    /**
     * Creates and enqueues node for current thread and given mode.
     *
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
    private Node addWaiter(Node mode) {
    	//	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;
            //用cas把自己这个node设为尾节点,用cas是为了防止多个线程都想入队,保障			 //了线程安全,用cas保证了一次就一个节点进入队列,然后这个节点变成了队列的             //新的尾节点
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                //节点入队后,返回包装当前线程的节点
                return node;
            }
        }
        //队列为空的话或者上面的cas入队失败,就是下面的入队方式,最终返回的还是包装当         //前线程的节点
        enq(node);
        return node;
    }

private Node enq(final Node node)

cas入队方法

private Node enq(final Node node) {
		//先来一个自旋
        for (;;) {
        	//获得等待队列的尾节点
            Node t = tail;
            //队列为空的情况
            if (t == null) { // Must initialize
            	//初始化其实也是初始化一个dummy节点,头指针和尾指针都指向dummy节           	   //点,但是这个dummy节点里面是不含线程的,dummy节点只是为了方便操 				  //作等待队列
                if (compareAndSetHead(new Node()))
                    tail = head;
            //队列不为空的情况,那么就cas入队
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    //这里才返回,返回的是包装了当前线程的节点
                    return t;
                }
            }
        }
    }

实现不可重入锁

import lombok.extern.slf4j.Slf4j;

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

@Slf4j(topic = "ch.TestAqs01")
public class TestAqs01 {
    //测试MyLock
    public static void main(String[] args) {
        MyLock myLock = new MyLock();
        new Thread(() -> {
            myLock.lock();
            log.debug("加锁");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            myLock.unlock();
            log.debug("解锁");
        }, "t1").start();
        new Thread(() -> {
            myLock.lock();
            log.debug("加锁");
            myLock.unlock();
            log.debug("解锁");
        }, "t2").start();
    }
}

@Slf4j(topic = "ch.MyLock")
class MyLock implements Lock {

    class MySync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        public Condition newCondition() {
            return new ConditionObject();
        }
    }

    private MySync mySync = new MySync();

    //加锁
    @Override
    public void lock() {
        mySync.acquire(1);
    }

    //加可打断的锁
    @Override
    public void lockInterruptibly() throws InterruptedException {
        mySync.acquireInterruptibly(1);
    }

    //尝试加锁
    @Override
    public boolean tryLock() {
        return mySync.tryAcquire(1);
    }

    //带超时的尝试加锁
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return mySync.tryAcquireNanos(1, unit.toNanos(time));
    }

    //解锁
    @Override
    public void unlock() {
        mySync.release(1);
    }

    //创建条件变量
    @Override
    public Condition newCondition() {
        return mySync.newCondition();
    }
}

AQS的基本思想

获取锁的逻辑

while(state 状态不允许获取) {
 if(队列中还没有此线程) {
 入队并阻塞
 }
}
当前线程出队

释放锁的逻辑

if(state 状态允许了) {
 恢复阻塞的线程(s)
}

设计的要点如下:
第一是要原子维护state的状态
state使用volatile配合cas保证修改时的原子性
state采用了32bit的int型数据来维护同步状态,因为long型的数据在很多平台下可能会有线程安全问题。(long可能线程不安全的原因:JVM将32位数据作为原子操作,所以读写long类型的数据,很可能是需要两次的读写操作,如果是多个线程一起操作同一个long型数据,很可能出现高32位和低32位错误的情况)

第二是要设计好阻塞以及恢复线程的方式
*早期的控制线程暂停和恢复api有suspend和resume,但是他们有弊端,如果先调用resume,suspend将感知不到。(resume,suspend过期的线程阻塞启动的方法)
*现在线程的阻塞以及恢复可以通过park和unpark实现
*park和unpark是针对线程的,而不是针对同步器的,控制粒度更加精细(同步器一般是指多线程的控制器,等待一个或者多个线程变成某一状态时,某些线程再开启下一部分的操作)
*park线程还可以通过interrupt打断

第三是做好队列设计
AQS使用了fifo先入先出队列,并不支持优先级队列
设计时借鉴了CLH队列,是一种单向无锁的队列

AQS队列中只有head和tail两个指针节点,都用volatile配置cas使用,保证了多线程下的安全性,每个节点都有state维护节点状态。

ReentrantLock原理

非公平锁的实现原理

加锁以及竞争加锁的流程

首先从构造器开始看,ReentrantLock默认是非公平锁实现。

    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    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() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

NonfairSync继承了Sync,Sync又继承了AbstractQueuedSynchronizer


当没有竞争时,线程直接对资源加锁,把state设为1,然后将owner线程设置为本线程。


在有竞争的情况下,假如线程一尝试用CAS将state由0改为1,结果失败了,
这时就会进入acquire(1)的分支
acquire的方法如下

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

首先进行的操作还是尝试去获得锁,但是由于state状态为1,所以仍然失败,
失败之后进入addWaiter方法,构造Node队列
addWaiter的主要目的就是建立一个等待资源的队列,其中头节点是个dummy节点,且队列刚初始化时,每个节点的waitStatus都是0

接着线程进入acquireQueued的方法
1.acquireQueued会在一个死循环中不断尝试去获得锁,如果失败了就进入park阻塞
2.如果自己是head节点后的节点,即等待队列的首节点,就会再次去尝试去获得锁,这时state仍为1,所以尝试获取锁失败。
3.获取锁失败后进入的是shouldParkAfterFailedAcquire逻辑,就是把线程park,并且将线程的前驱节点的waitStatus设置为signal。第一次进入shouldParkAfterFailedAcquire逻辑会返回false
4.shouldParkAfterFailedAcquire返回false之后仍会回到acquireQueued中的死循环,这时候由于等待队列只有自己一个真。等待节点,所以会再去获取一次锁,当然由于state仍为1,所以尝试仍旧失败
5.尝试获取锁失败后,又会进入shouldParkAfterFailedAcquire逻辑,这一次由于前驱是signal,所以shouldParkAfterFailedAcquire直接返回true
6.shouldParkAfterFailedAcquire直接返回true后就会进入parkAndCheckInterrupt,当前线程终于进入了park状态

如果有多个线程经历过上面这样的竞争线程失败,就会变成下图:

解锁以及唤醒的流程

thread-0用完资源之后释放锁,进入release流程

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;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
        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.
         */
        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;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

可以看到首先是改变state状态,如果将state状态修改到0,就返回release成功,并且将ExclusiveOwnerThread设置为null
此时当前队列不为空,且head的waitStatus=-1,head要负责唤醒它的后继节点,进入unparkSuccessor流程。
找到队列中离head最近,且waitStatus < 0且不为null的node,然后用LockSupport的unpark方法恢复这个node中线程的运行,这个例子中是thread-1

unpark后,thread-1会进入acquireQueued的流程,
这时如果加锁成功(没有竞争),
会设置ownerThread为thread-1,state=1
head会指向刚刚thread-1所在的node,该node也会清空thread,变成一个dummy型的头节点
原本的head会从链表上断开,从而被垃圾回收

另外一种情况,如果这时候,也有其他线程来竞争锁,假如这时候thread4来了(因为是非公平锁)
thread-4会被设置为ownerthread,state又改成了1
thread-1会再次进入acquireQueued流程,且因为state为1又会竞争锁失败,重新进入park阻塞

注意一点:是否需要unpark是由当前节点的前驱节点的waitStatus是否等于signal决定的,而不是由本节点的waitStatus确定的。

ReentrantLock的可重入原理

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;
        }

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //支持锁重入,只有state减为0的时候,才释放成功
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

ReentrantLock的可打断原理

不可打断模式:在这个模式下,即使线程被打断,仍然会驻留在AQS队列中,一直要等到获得锁之后才知道自己被打断了

private final boolean parkAndCheckInterrupt() {
        // 如果打断标记已经是 true, 则 park 会失效
        LockSupport.park(this);
        // interrupted 会清除打断标记
        return Thread.interrupted();
    }

    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;
                    failed = false;
                    // 还是需要获得锁后, 才能返回打断状态
                    return interrupted;
                }
                if (
                    shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt()
                ) {
                    // 如果是因为 interrupt 被唤醒, 返回打断状态为 true
                    interrupted = true;
                }
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    public final void acquire(int arg) {
        if (
            !tryAcquire(arg) && 
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
        ) {
            // 如果打断状态为 true
            selfInterrupt();
        }
    }

    static void selfInterrupt() {
        // 重新产生一次中断
        Thread.currentThread().interrupt();
    }
}

可打断模式:其他线程一旦打断,就会立即捕获打断异常

static final class NonfairSync extends Sync {
    public final void acquireInterruptibly(int arg) throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        // 如果没有获得到锁, 进入 (一)
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

    // (一) 可打断的获取锁流程
    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()) {
                    // 在 park 过程中如果被 interrupt 会进入此
                    // 这时候抛出异常, 而不会再次进入 for (;;)
                    throw new InterruptedException();
                }
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
}

公平锁实现原理

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) {
                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;
        }
    }

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

    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());
    }

一个简单的demo演示公平锁的原理

import java.util.concurrent.locks.ReentrantLock;

public class TestFairReentrantLock {
    public static void main(String[] args) {
        //创建一个公平锁
        ReentrantLock fairLock = new ReentrantLock(true);
        Thread t1 = new Thread(() -> {
            fairLock.lock();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            fairLock.unlock();
        }, "t1");
        Thread t2 = new Thread(() -> {
            fairLock.lock();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            fairLock.unlock();
        }, "t2");
        Thread t3 = new Thread(() -> {
            fairLock.lock();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            fairLock.unlock();
        }, "t3");
        //公平锁就是t2在排队的时候,t1释放锁,t3尝试获得锁,t3必须得后于t2获得锁,才叫公平
        t1.start();
        t2.start();
        t3.start();
    }
}

第一步main线程执行到函数末尾,即三个线程都start,但是三个线程的断点都停在fairLock.lock();的状态


第二步让t1线程运行到unlock这一步,但是却不释放锁

第三步,把线程切换到thread-2,单步一直走,进入acquireQueued方法可以发现线程进入等待队列并被park了起来


第四步,thread-1进行unlock,但是thread-2不动,thread-3去尝试获取锁,由于thread-1解锁了,所以thread-3可以读到state的值为0,但是在hasQueuedPredecessors函数中,由于队列中thread2线程在thread-3线程之前,所以thread-3没法执行,thread-3只能进入队列在thread-2之后

//head的next指向thread-2

//thread-2的next指向thread-3

这就是公平锁

条件变量的实现原理

每个条件变量其实就对应着一个等待队列,实现类是ConditionObject

await流程

开始的时候thread-0持有锁,然后调用await函数,进入ConditionObject的addConditionWaiter的流程
创建新的Node状态为Node.CONDITION,关联Thread-0,进入等待队列尾部

然后进入AQS的fullRelease函数,释放同步器上的锁

thread-0所在的节点会unpark AQS中thread-0之后一个节点中的线程,如果没有其他线程竞争锁,那么thread-1竞争锁成功。

signal流程

假设thread-1要来唤醒thread-0

thread-1会进入conditionObject的doSignal流程,取得等待队列中第一个Node,就是thread-0所在的Node

然后执行transferForSignal流程,将该Node加入AQS队列尾部,将Thread-0的waitStatus改为0,Thread-3的waitStatus改为-1.

记录一次await&signal的源码调试

思路

main线程中创建三个Thread,分别是t1,t2,t3,两个condition,分别是conditionA,conditionB,t1,t2进入conditionA等待,t3进入conditionB等待,最后主线程再signalAll conditionA中的线程,signal conditionB中的线程。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class TestReentrantLockAwait {
    public static void main(String[] args) {
        ReentrantLock myLock = new ReentrantLock();
        Condition conditionA = myLock.newCondition();
        Condition conditionB = myLock.newCondition();
        Thread t1 = new Thread(() -> {
            myLock.lock();
            try {
                Thread.sleep(1000);
                conditionA.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            myLock.unlock();
        }, "t1");
        Thread t2 = new Thread(() -> {
            myLock.lock();
            try {
                Thread.sleep(1000);
                conditionA.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            myLock.unlock();
        }, "t2");
        Thread t3 = new Thread(() -> {
            myLock.lock();
            try {
                Thread.sleep(1000);
                conditionB.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            myLock.unlock();
        }, "t3");
        t1.start();
        t2.start();
        t3.start();
        myLock.lock();
        conditionA.signalAll();
        conditionB.signal();
        myLock.unlock();
    }
}

main线程执行完t1,t2,t3的start方法,创建三个线程,然后线程t1,t2,t3分别去竞争myLock。达到t1线程占有锁,t2,t3线程被unpark在等待队列中的状态。
//t1竞争锁成功


//t2竞争锁失败,进入等待队列,自己被park阻塞,进入WAIT状态;t3当然同理且就在等待队列的t2的后面



//t1调用await方法,观察源码实现,首先进入await(),接着进入addConditionWaiter(),在addConditionWaiter方法中,由于t1是第一个调用await方法的线程,所以会创建一个conditionA的等待队列,且waitStatus为CONDITION(-2),然后进入fullyRelease()方法释放锁资源,fullyRelease()中会调用release()方法(主要是tryRelease释放锁,unparkSuccessor唤醒等待锁的线程,在这个例子中就是先唤醒t2),t2被唤醒后没有竞争就会占有锁,并且把head的next置为t3,此时t1在conditionA的等待队列,t2占有锁,t3在等待锁的队列




//然后t2进入conditionA的await方法,此时由于t1在conditionA的等待队列,所以t2只能进入conditionA的等待队列并排在t1后,从图中可以看出firstWaiter是t1,firstWaiter.nextWaiter是t2,接着t2也被park,t3终于获得了锁,然后t3去调用conditionB的await类似之前t1调用conditionA的await方法。这个时候t1,t2在等conditionA,t3在等conditionB,只有main线程运行,我们就可以看看signal的源码了



//最后来看signal的源码,conditionA调用signalAll(),signalAll()先检查main线程是否占有锁,占有才能去做unpark,doSignalAll方法就是把所有等待在conditionA的线程一个接一个的放到等待锁的队列上,signal的操作类似


//第一次循环

//第二次循环

posted @   理塘DJ  阅读(46)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示