AQS独占ReentrantLock源码和共享CountDownLatch源码分析

58 篇文章 0 订阅
3 篇文章 0 订阅

目录

 Condition使用demo,实现condition的分组唤醒

首先了解一下AQS

分析独占锁ReentrantLock加锁源码和condition唤醒源码

分析共享锁CountDownLatch源码


 Condition使用demo,实现condition的分组唤醒

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Description  实现三个线程环状执行任务   1-》2-》3-》1
 * @Author lixinyu
 * @Date 2021/1/23 21:36
 * @Version 1.0
 **/
public class Main2 {
    private Lock lock = new ReentrantLock();
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();
    private AtomicInteger i = new AtomicInteger(1);

    public static void main(String[] args) {
        Main2 main2 = new Main2();

        new Thread(() -> {
           int count = 5;
            while (count-- > 0) {
                main2.printc1();
            }
        }).start();
        new Thread(() -> {
            int count = 5;
            while (count-- > 0) {
                main2.printc2();
            }
        }).start();
        new Thread(() -> {
            int count = 5;
            while (count-- > 0) {
                main2.printc3();
            }
        }).start();
    }

    public void printc1() {
        lock.lock();
        try {
            //1.让当前线程等待在condition1上
            // 调用await方法首先要确保当前线程已经获得了condition关联锁的使用权,否则会报错
            // IllegalMonitorStateException
            if (i.get() != 1) {
                c1.await();
            }
            //2.⼲活
            System.out.println(Thread.currentThread().getName() + "\t" + i);
            //通知等在第二个条件上的线程起立
            i.compareAndSet(1, 2);
            c2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printc2() {
        lock.lock();
        try {
            //1.让当前线程等待在condition1上
            // 调用await方法首先要确保当前线程已经获得了condition关联锁的使用权,否则会报错
            // IllegalMonitorStateException
            if (i.get() != 2) {
                c2.await();
            }
            //2.⼲活
            System.out.println(Thread.currentThread().getName() + "\t" + i);
            //通知等在第三个条件上的线程起立
            i.compareAndSet(2, 3);
            c3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printc3() {
        lock.lock();
        try {
            //1.让当前线程等待在condition1上
            // 调用await方法首先要确保当前线程已经获得了condition关联锁的使用权,否则会报错
            // IllegalMonitorStateException
            if (i.get() != 3) {
                c3.await();
            }
            //2.⼲活
            System.out.println(Thread.currentThread().getName() + "\t" + i);
            //通知等在第一个条件上的线程起立
            i.compareAndSet(3, 1);
            c1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}


输出
Thread-0	1
Thread-1	2
Thread-2	3
Thread-0	1
Thread-1	2
Thread-2	3
Thread-0	1
Thread-1	2
Thread-2	3
Thread-0	1
Thread-1	2
Thread-2	3

首先了解一下AQS

AbstractQuenedSynchronizer抽象的队列式同步器,ReentrantLock、CountDownLatch等工具类的基础

CLHCraigLandinand Hagersten)队列是一个虚拟的双向队列,节点之间的关联关系

AQS是将每一条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node),来实现锁的分配,基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒

AQS 定义了两种资源共享方式:
1.Exclusive:独占,只有一个线程能执行,如ReentrantLock
2.Share:共享,多个线程可以同时执行,如SemaphoreCountDownLatchReadWriteLockCyclicBarrier

分别对应AQS类中未实现的方法,tryAcquire \ tryReleasetryAcquireShared \ tryReleaseShared。以上各个工具类内部Sync就是实现了这几个方法

 // 模板模式  
 protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

 

分析独占锁ReentrantLock加锁源码和condition唤醒源码

ReentrantLockcondition我理解的流程就是ReentrantLock存在一个同步队列,队列中存放lock()方法没有获取到锁的线程,conditon中存在一个等待队列,存放condition.await()等待的线程。在线程获取lock锁后,调用condition.await(),会将当前线程放入condition对象的等待队列中,并释放所持有的lock锁。别的线程调用condition.signal()后会将condition等待队列中队列头一个线程转到lock锁的同步队列中,使得它拥有获得锁的机会。

 

先来lock()的源码

按非公平锁来,非公平和公平区别在加锁中公平锁只允许同步队列头部的线程获取锁

加锁过程 找到ReentrantLock内部AQS实现类 NonfairSync.lock()

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() {

        // 如果当前没人加锁,cas加锁成功表示获取锁成功
        if (compareAndSetState(0, 1))

        // 设置当前线程为锁持有者
            setExclusiveOwnerThread(Thread.currentThread());
        else
             // 加锁未成功
            acquire(1);
    }

在进入acquire(1)方法

位置java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire

public final void acquire(int arg) {

    // tryAcquire()方法由子类NonfairSync实现
    if (!tryAcquire(arg) &&

    // 加入同步队列自旋等待锁
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

先来看tryAcquire()方法实现

位置java.util.concurrent.locks.ReentrantLock.NonfairSync#tryAcquire

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

    // 再cas争取一下锁
        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;
}

回到acquire方法,未获取锁会继续进入acquireQueued方法,首先先把当前线程组成一个Node,放入队列

位置java.util.concurrent.locks.AbstractQueuedSynchronizer# addWaiter

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

// enq是完整的入队操作,上边的if算个尝试,乐观的认为不需要循环搞
    enq(node);
    return node;
}

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

位置java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireQueued

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;

         // 自旋获取锁
        for (;;) {
             // 同步队列的第一个节点 head是空的,不对应线程
            // 获取当前线程节点的上一个节点,如果上一个节点是head,说明咱排到了第一个
            // 那么尝试获取一下锁,tryAcquire代码在上边
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                // 获取成功
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }

            // 上个节点不是头节点,表明新节点还排不上,看看是不是睡一会等叫号
            if (shouldParkAfterFailedAcquire(p, node) &&
                // park调用LockSupport.park(this);睡眠当前线程
                // 醒来后继续自旋获取锁
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {

        // 如果获取锁失败并且退出了for循环,出现了异常情况不需要锁了,将节点状态改为取消
        if (failed)
            cancelAcquire(node);
    }
}

位置java.util.concurrent.locks.AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire

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;



// 检查当前状态,是否需要睡一会

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;

       // 新建的Node状态为0,表示自旋等着呢

    // 上个节点状态为signal表示等待别的线程释放锁,那我这个新节点的线程不着急了就,此时可以睡
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;

    // 大于0表示上个节点状态是取消,此时不需要管它了,再往上找,顺便把这种取消的清理掉(取消状态的更新在上边acquireQueued方法finally中)
    if (ws > 0) {
        /*
         * 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.
         */

    // 把上个节点状态改为等待唤醒,下次进入此方法就睡眠了
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

解锁过程 从ReentrantLock#unlock找到

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

位置java.util.concurrent.locks.AbstractQueuedSynchronizer#release

public final boolean release(int arg) {

    // tryRelease由ReentrantLock.Sync子类实现
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)

            // 叫醒同步队列中排第一个的线程
            unparkSuccessor(h);
        return true;
    }
    return false;
}

 

位置java.util.concurrent.locks.ReentrantLock.Sync#tryRelease

protected final boolean tryRelease(int releases) {

    //  0为未加锁,大于0为已加锁
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {

    // 等于0表示锁释放完了,清空锁持有者
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

位置java.util.concurrent.locks.AbstractQueuedSynchronizer#unparkSuccessor

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

Lock和UnLock方法完了,接着搞Condition的await和signal方法

先看newCondition(),找到了AbstractQueuedSynchronizer.ConditionObject中,为AQS的内部类,也就是ReentrantLock的Sync和condition是关联的。

ConditionObject.await()方法

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();

// 将当前线程放入condition的等待队列中
    Node node = addConditionWaiter();


// 释放当前关联的sync.lock锁
    int savedState = fullyRelease(node);

    int interruptMode = 0;

// 如果没在lock锁的同步队列中,睡会等待
// 什么时候会在同步队列中?答:其他线程调用了condition.signal
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }


// 熟悉的方法,上边说过了,自旋获取锁,获取不到睡一会
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

也就是await方法睡眠时示范关联的lock锁,加入condition等待队列,醒来后被加入到了lock的同步队列,再自旋拿到锁

再看signal()

位置java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject#signal

public final void signal() {

// 当前线程没有持有锁,会报错
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();

// 取等待队列排第一个的线程
    Node first = firstWaiter;
    if (first != null)

// 唤醒,其实就是把这个线程加入到sync锁的同步队列
        doSignal(first);
}



private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}



final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

至此ReentrantLock分析完了

 

准备再以countDownLatch源码为例,分析一下共享锁的代码

分析共享锁CountDownLatch源码

典型用法1、实现多个线程同时执行。new CountDownLatch(1).多个线程执行前先await(), 主线程调用countdown(),多个线程同时唤醒开始执行

典型用法2、实现主线程等待多个线程执行完毕。new CountDownLatch(5)。主线程await(),等待其他五个线程countdown()。再开始执行。

 

来看源码,先来看tryAcquireShared \ tryReleaseShared两个方法在countDownLatch中的实现。

位置java.util.concurrent.CountDownLatch.Sync

       protected int tryAcquireShared(int acquires) {
            // 为0表示锁拿到了,await的线程可以继续执行
            return (getState() == 0) ? 1 : -1;
        }

        protected boolean tryReleaseShared(int releases) {
            // 这个很简单,对state进行减1操作,只有最后一次减到0时才返回true
            // 返回true会去唤醒所有等待线程
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

 

再来看CountDownLatch#await()

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

位置java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireSharedInterruptibly

   public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();

        // try看看是否state值为0了,不为0自旋获取,一段时间后睡眠
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
       
        // 包装成一个等待节点 
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                // 自旋获取上个节点,看看是不是自个排到了第一个
                final Node p = node.predecessor();
                if (p == head) {
                    // 如果是第一个,尝试获取锁(看看state是否为0了)
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        // 为0表示自旋可以退出了
                        // 这个方法看下边,主要是唤醒其他等待线程
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
             // 这个和ReentrantLock差不多,都是看看上个节点什么状态,当前节点是否可以睡眠等待
             // 等到最后一个countdown,state变为0时将它唤醒,从而继续自旋获取
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

其中和排他锁不一样的是,等待队列头节点出队时,会唤醒等待队列中后继节点,然后后继节点再依次唤醒其他后继节点,在setHeadAndPropagate方法中

   private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        
        // 传进来的propagate是1,所以如果node.next != null 表示还有等待的,那就继续唤醒下一个节点
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

    private void doReleaseShared() {
  
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases

                    // 这个方法唤醒后继节点,正儿八经唤醒,上边列过代码了
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

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

再看countdown()。

    public void countDown() {
        sync.releaseShared(1);
    }
    public final boolean releaseShared(int arg) {
        // 这个try只有最后一个把state置为0的线程才会返回true
        if (tryReleaseShared(arg)) {
            // 释放共享锁,把等待队列头一个放出来,代码在上边
            doReleaseShared();
            return true;
        }
        return false;
    }

把头一个节点放出来以后,这个节点会自旋发现state为0了,再把它的后继节点放出来,这样链式唤醒所有等待线程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值