体系化深入学习并发编程(九)并发控制与AQS

我们经常需要处理线程间的协作问题,比如哪些线程先执行,哪些线程后执行,或者哪些线程需要一起执行。
之前的文章中,用到了CountDownLatch等类来控制并发流程,现在就来详细地了解一下有关控制并发流程的类。

控制并发流程的工具类

CountDownLatch

CountDownLatch,翻译过来就是倒计时的闩锁。等待计数达到要求后才执行下一操作。
生活中处处都能见到类似的场景,比如网络购物参加团购,需要人数达到多少才能完成下单。
CountDownLatch就是起到计数的这个作用。
CountDownLatch
CountDownLatch的主要方法:

  • public CountDownLatch(int count),构造方法,必须传入计数的个数
  • await() /await(long timeout, TimeUnit unit) 如果不传入时间,就一直等待count为0。如果传入等待时间,时间到了count不为0也会继续执行。
  • countDown() 计数减一

CountDownLatch的两种用法:

A CountDownLatch is a versatile synchronization tool and can be used for a number of purposes. A CountDownLatch initialized with a count of one serves as a simple on/off latch, or gate: all threads invoking await wait at the gate until it is opened by a thread invoking countDown(). A CountDownLatch initialized to N can be used to make one thread wait until N threads have completed some action, or some action has been completed N times.

下面就演示游乐场玩过山车,需要满10个人才能发车。
用线程池来实现,线程池中的两个线程就像是2个入口。

public static void main(String[] args) throws InterruptedException {
    CountDownLatch latch = new CountDownLatch(10);
    ExecutorService threadpool = Executors.newFixedThreadPool(2);
    for (int i = 0; i <10; i++) {
        int finalI = i+1;
        threadpool.execute(()->{
            try {
                Thread.sleep((long) (Math.random()*1000));
                System.out.println(finalI+"号游客正在等待");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                latch.countDown();
            }
        });
    }
    threadpool.shutdown();
    System.out.println("开始接待游客");
    latch.await();
    System.out.println("游客满10人,可以发车了");
}
开始接待游客
1号游客正在等待
3号游客正在等待
2号游客正在等待
4号游客正在等待
6号游客正在等待
5号游客正在等待
7号游客正在等待
8号游客正在等待
10号游客正在等待
9号游客正在等待
游客满10人,可以发车了

这种用法是:线程A等待其他线程都运行了,线程A才继续运行。

下面演示另一种用法:线程A作为门闩,其他线程都到了,才放行。
模拟一下赛马比赛,有8匹马进行赛马比赛,比较出8匹马的快慢顺序。

public static void main(String[] args) throws InterruptedException {
    CountDownLatch latch = new CountDownLatch(1);
    ExecutorService threadPool = Executors.newFixedThreadPool(8);
    for (int i = 0; i <8; i++) {
        int finalI = i+1;
        threadPool.execute(()->{
            try {
                System.out.println(finalI +"号马准备就绪");
                latch.await();
                Thread.sleep((long) (Math.random()*10000));
                System.out.println(finalI +"号马到达终点");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
    threadPool.shutdown();
    Thread.sleep(1000);
    System.out.println("比赛开始");
    latch.countDown();
    System.out.println("=============");
}

因为作为门闩,只需要传入1作为开关。
每匹马到的时候执行准备就绪就开始等待。
当countDown执行,count=0时,所有线程同时运行。

总结:CountDownLatch的两种适用场景

  • 一等多
  • 多等一

Semaphore

信号量的概念我们在操作系统的学习中也会接触到,起到管理有效资源的作用。
在Java中也有Semaphore,它维持一组许可证,许可证的数量限定了能同时获取资源的线程个数,只有获得了许可证的线程才能继续访问资源,而没有获取到的线程就会被阻塞,直到其他线程归还许可证。这是一种类锁的概念。

A counting semaphore. Conceptually, a semaphore maintains a set of permits. Each acquire() blocks if necessary until a permit is available, and then takes it. Each release() adds a permit, potentially releasing a blocking acquirer. However, no actual permit objects are used; the Semaphore just keeps a count of the number available and acts accordingly.

Semaphore的主要方法:

  • public Semaphore(int permits, boolean fair) 构造方法,可以选择是否公平。
  • acquire() 获取许可证,可以传入参数,获取许可证的数量
  • acquireUninterruptibly() 获取许可证且不可被打断
  • tryAcquire() 尝试获取许可证,也可以传入时间参数
  • release() 归还许可证,可以传入参数,释放许可证的数量

下面演示一下Semaphore的用法:
设置4个许可证,每次线程需要获取两个,所以最多只有两个线程能同时执行任务。其他线程会等待许可证。

static Semaphore semaphore = new Semaphore(4,true);
public static void main(String[] args) {
    ExecutorService threadPool = Executors.newFixedThreadPool(10);
    for (int i = 0; i <10; i++) {
        threadPool.execute(()->{
            try {
                semaphore.acquire(2);
                System.out.println(Thread.currentThread().getName()+"获取到许可证");
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println(Thread.currentThread().getName()+"释放了到许可证");
                semaphore.release(2);
            }
        });
    }
    threadPool.shutdown();
}
pool-1-thread-1获取到许可证
pool-1-thread-2获取到许可证
pool-1-thread-1释放了到许可证
pool-1-thread-2释放了到许可证
pool-1-thread-5获取到许可证
pool-1-thread-6获取到许可证
pool-1-thread-5释放了到许可证
pool-1-thread-4获取到许可证
...

需要注意的是,许可证的释放最好和获取的数量一致,否则会导致许可证减少而引起其他线程一直被阻塞。
通常,用于控制资源访问的信号量应该被设置为公平,以确保线程没有被访问资源,否则需要注意线程饥饿的风险。
同时,信号量是没有所有权的概念的,也就是说许可证是可以被其他线程释放的,除了持有许可证线程外,其他的线程也能来释放许可证,在某些场景下,可以用于死锁恢复。

Condition

Condition接口又叫条件对象,他将对象的wait、notify、notifyall这些monitor方法分解到不同的对象,它是绑定在不同效果的Lock上的,该接口实例只能使用Lock中的newCondition() 方法来获取。
也就是用Lock取代synchronized的使用,Condition取代wait()、notify()、notifyall()的使用。(因为执行这些方法,必须获得当前对象的monitor锁)

Condition factors out the Object monitor methods (wait, notify and notifyAll) into distinct objects to give the effect of having multiple wait-sets per object, by combining them with the use of arbitrary Lock implementations. Where a Lock replaces the use of synchronized methods and statements, a Condition replaces the use of the Object monitor methods.
A Condition instance is intrinsically bound to a lock. To obtain a Condition instance for a particular Lock instance use its newCondition() method.

Condition的主要方法:

  • await(),可以传入时间参数
  • signal() 唤醒一个等待线程
  • signalAll() 唤醒所有的等待线程

下面演示它的用法:

public class ConditionDemo1 {
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    private static void Method1(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"等待条件");
            condition.await();
            System.out.println("条件满足,"+Thread.currentThread().getName()+"继续执行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    private static void Method2(){
        lock.lock();
        try {
            Thread.sleep((long) (Math.random()*1000));
            System.out.println(Thread.currentThread().getName()+"完成条件,唤醒等待线程");
            condition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->Method1(),"线程1").start();
        Thread.sleep(1);
        new Thread(()->Method2(),"线程2").start();
    }
}
线程1等待条件
线程2完成条件,唤醒等待线程
条件满足,线程1继续执行

之前我们用wait()、notify()和阻塞队列都实现过生产者消费者模式,现在就使用Condition也来实现一次

public class ProducerConsumerCondition {
    private final static Integer QUEUE_SIZE=5;
    private static ReentrantLock lock = new ReentrantLock();
    private static Queue<Integer> queue = new PriorityQueue<>(QUEUE_SIZE);
    private static Condition condition = lock.newCondition();

    private static class Producer implements Runnable{

        @Override
        public void run() {
            while (true){
                lock.lock();
                try {
                    //队列没满就一直放,满了就通知消费者
                    while (!(queue.size()==QUEUE_SIZE)){
                        queue.add(1);
                        System.out.println("生产者添加商品,剩余库存:"+queue.size());
                    }
                    System.out.println("生产完毕,等待消费者消费");
                    condition.signal();
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }
    private static class Consumer implements Runnable{

        @Override
        public void run() {
            while (true){
                lock.lock();
                try {
                    //队列没空就一直消费,空了就通知生产者
                    while (!(queue.size()==0)){
                        queue.poll();
                        System.out.println("消费者购买商品,剩余库存:"+queue.size());
                    }
                    System.out.println("购买完毕,等待生产者生产");
                    condition.signal();
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new Producer()).start();
        new Thread(new Consumer()).start();
    }
}
生产者添加商品,剩余库存:1
生产者添加商品,剩余库存:2
生产者添加商品,剩余库存:3
生产者添加商品,剩余库存:4
生产者添加商品,剩余库存:5
生产完毕,等待消费者消费
消费者购买商品,剩余库存:4
消费者购买商品,剩余库存:3
消费者购买商品,剩余库存:2
消费者购买商品,剩余库存:1
消费者购买商品,剩余库存:0
购买完毕,等待生产者生产
...

CyclicBarrier

CyclicBarrier循环栅栏和CountDownLatch有些相似,它是一个可重用的栅栏,可以将一组线程拦截在一个集合点后,这一组线程再次启动。
CyclicBarrier的构造方法中也可以传入runnable对象,即等线程都到了执行了任务再启动线程

下面来模拟这一场景:
三个小伙伴约好去春熙路吃火锅,要三个人都到齐了,才进去开始吃火锅。同样,三个人都吃好了,再一起离开。

public class CyclicBarrierDemo {
    private static CyclicBarrier barrier = new CyclicBarrier(3,()-> System.out.println("三个人都ok了"));

    private static void eatHotPot(){
        String name = Thread.currentThread().getName();
        System.out.println(name+"出发了");
        try {
            Thread.sleep((long) (Math.random()*10000));
            System.out.println(name+"到达火锅店");
            barrier.await();
            System.out.println(name+"进店开吃");
            Thread.sleep((long) (Math.random()*10000));
            System.out.println(name+"吃好了");
            barrier.await();
            System.out.println(name+"离开火锅店");
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        new Thread(()->eatHotPot(),"赵篮球").start();
        new Thread(()->eatHotPot(),"王足球").start();
        new Thread(()->eatHotPot(),"张排球").start();
    }
}
王足球出发了
赵篮球出发了
张排球出发了
张排球到达火锅店
王足球到达火锅店
赵篮球到达火锅店
三个人都ok了
赵篮球进店开吃
王足球进店开吃
张排球进店开吃
赵篮球吃好了
王足球吃好了
张排球吃好了
三个人都ok了
张排球离开火锅店
赵篮球离开火锅店
王足球离开火锅店

可以看到,每次barrier被使用了两次,同样,线程都是以三个为一组操作,没有哪个线程先自己跑完。

这也是CyclicBarrier和CountDownLatch的差别:

  • CyclicBarrier是可以重用的,CountDownLatch不能重用
  • CyclicBarrier是以线程为主体的,每个线程调用CyclicBarrier实例对象的await()方法就开始等待。而CountDownLatch是以事件为主体的,主要是countDown()方法的执行,一个线程可以多次执行该方法

AQS

AQS全称是:AbstractQueuedSynchronizer

Provides a framework for implementing blocking locks and related synchronizers (semaphores, events, etc) that rely on first-in-first-out (FIFO) wait queues. This class is designed to be a useful basis for most kinds of synchronizers that rely on a single atomic int value to represent state. Subclasses must define the protected methods that change this state, and which define what that state means in terms of this object being acquired or released. Given these, the other methods in this class carry out all queuing and blocking mechanics. Subclasses can maintain other state fields, but only the atomically updated int value manipulated using methods getState(), setState(int) and compareAndSetState(int, int) is tracked with respect to synchronization.

在很多管理线程的类中我们都能看见这个类,通常是使用一个内部类Sync继承该类,在类中使用Sync的实例对象。

private static final class Sync extends AbstractQueuedSynchronizer 
private final Sync sync;

那么AQS的作用是什么?
AQS可以原子式地管理同步状态、阻塞和唤醒线程功能以及队列模型。
而这些功能,我们许多管理线程的类都需要用,所以把它抽取出来作为一个简单框架,这就是AQS的作用。

AQS的三个关键点

同步状态state

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

一个volatile修饰的整型类型,不同的工具类中有不同的含义,之前在锁的文章中,关于可重入锁中,它表示“重入锁的数量”。在Semaphore中表示“剩余许可证的数量”。

FIFO队列
FIFO队列是对线程进行管理,就像是一个线程管理器,将暂时阻塞的线程就放在队列中,获取不到锁的线程就在队列中排队。
FIFO队列是一个双向链表类型的队列
FIFO队列

获取/释放方法
这个需要不同的工具类实现不同的功能,是获取state的操作。
比如Semaphore中的acquire方法,去获取许可证,或者是CountDownLatch中的await方法,等待(获取)计数为0的状态。
有获取,就有释放。
比如对许可证的释放,减少计数countdown,解锁等操作。

CountDownLatch中的AQS

首先是CountDownLatch的构造方法

//传入计数器count
public CountDownLatch(int count) {
	//判断是否非负
    if (count < 0) throw new IllegalArgumentException("count < 0");
    //调用sync的构造方法
    this.sync = new Sync(count);
}

Sync的构造方法,调用的是setState()

Sync(int count) {
    setState(count);
}

这是AQS中的方法

protected final void setState(int newState) {
    state = newState;
}

也就是传入的计数器count的值,最后赋值给state。

再看看getCount方法

public long getCount() {
    return sync.getCount();
}

和上面一样,调用的是getState()的方法,最后获取到state

int getCount() {
    return getState();
}
protected final int getState() {
    return state;
}

然后是CountDownLatch的两个核心的方法
countDown()

public void countDown() {
    sync.releaseShared(1);
}

调用sync的releaseShared()方法

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

判断tryReleaseShared()方法

protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
    	//获取当前的计数
        int c = getState();
        if (c == 0)
            return false;
        //如果不为0,减1
        int nextc = c-1;
        //cas替换,不成功就继续自旋
        if (compareAndSetState(c, nextc))
        	//成功后判断count是否等于0
            return nextc == 0;
    }
}

当nextc=0时会返回true,此时就会调用doReleaseShared()
这个方法的目的是解锁所有的阻塞线程
for循环中取队列元素,调用unparkSuccessor()方法

private void doReleaseShared() {
    /*
     * Ensure that a release propagates, even if there are other
     * in-progress acquires/releases.  This proceeds in the usual
     * way of trying to unparkSuccessor of head if it needs
     * signal. But if it does not, status is set to PROPAGATE to
     * ensure that upon release, propagation continues.
     * Additionally, we must loop in case a new node is added
     * while we are doing this. Also, unlike other uses of
     * unparkSuccessor, we need to know if CAS to reset status
     * fails, if so rechecking.
     */
    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;
    }
}

unparkSuccessor(h)调用了了LockSupport.unpark(s.thread);

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

最后调用native的unpark()方法,唤醒等待的线程

public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

然后是await()方法

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

调用acquireSharedInterruptibly(),传入参数1

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //判断tryAcquireShared(1)是否小于0
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

回到CountDownLatch内部类Sync中的tryAcquireShared()方法

protected int tryAcquireShared(int acquires) {
	//当state==0时,返回1,否则返回-1
    return (getState() == 0) ? 1 : -1;
}

也就是只有state!=0时,才会调用doAcquireSharedInterruptibly()方法

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) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

调用addWaiter()将当前线程封装为Node对象,然后放入队列中,阻塞线程。
调用的parkAndCheckInterrupt()方法会调用LockSupport.park(),和之前的unpark()呼应

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

总结:
调用构造方法时传入count被赋值给state,在CountDownLatch中就是锁计数器。
调用await方法,会尝试获取共享锁而陷入等待。
当其他线程执行countDown方法,锁计数器==0时,所有阻塞的线程被唤醒然后继续运行。

Semaphore中的AQS

acquire方法

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

同样是调用了acquireSharedInterruptibly()这个方法
也是AQS中的方法

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

而在Semaphore中,有两个Sync的子类,分别是NonfairSyncFairSync,都各自实现了tryAcquireShared()方法
下面就用NonfairSync中的举例

protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
    	//获取当前可用许可证数
        int available = getState();
        //剩余许可证=可用许可证-去申请许可证
        int remaining = available - acquires;
        /**
        *如果剩余许可证<0,返回remaining这个负数
        *如果剩余许可证>=0,就CAS将可用许可证变为剩余许可证,失败则继续自旋判断,成功就返回remaining这个正数
        */
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

根据前面acquireSharedInterruptibly()方法,我们知道如果返回负数,会调用doAcquireSharedInterruptibly()方法导致线程阻塞,也就是获取许可证失败。
如果返回正数,就跳过该方法继续执行。

再来看看release()方法

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

跳转到releaseShared()方法,这个方法我们也看到过了

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

直接看Semaphore中tryReleaseShared()方法的实现

protected final boolean tryReleaseShared(int releases) {
    for (;;) {
    	//获取当前许可证数量
        int current = getState();
        //next=当前许可证+将要释放的数
        int next = current + releases;
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        //cas进行更新,失败则自旋。
        if (compareAndSetState(current, next))
            return true;
    }
}

总结:

  • 获取许可证:
    获取到可用的许可证,然后用可用许可证数-想要获取的许可证数,得到剩余许可证,CAS修改许可证数,失败则自旋。如果剩余许可证<0(获取失败),就进入等待队列。
  • 释放许可证:
    获取现有的许可证,然后把自身想要释放的许可证加回,CAS自旋写回。成功后唤醒队列中等待的下一个线程。

ReentrantLock中的AQS

开门见山,直接看ReentrantLock中的lock方法
它在ReentrantLock的Sync中,是一个抽象方法

abstract void lock();

同样我们就能回想起,它也是有公平和非公平的
两个内部子类:NonfairSyncFairSync

看看非公平的lock()方法

final void lock() {
	//cas期待锁状态为0(没线程持有),将它该为1
    if (compareAndSetState(0, 1))
    	//成功就调用这个方法,将当前线程设置为互斥锁持有线程
        setExclusiveOwnerThread(Thread.currentThread());
    else
    	//否则调用acquire
        acquire(1);
}

我们看看上锁失败调用的acquire()方法

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        //都为true就打断当前线程
        selfInterrupt();
}

首先是tryAcquire()的判定,它也被公平锁和非公平锁实现了,同样我们找到非公平锁中的

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

返回了nonfairTryAcquire()方法

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;
    }
    //如果获取失败返回false
    return false;
}

如果获取失败!tryAcquire(arg)这句话就是true,就跳转到第二个判定acquireQueued(addWaiter(Node.EXCLUSIVE), arg),也就是将当前线程封装为互斥Node节点,加入到等待队列中等待。

接下来是unlock()方法
调用的是sync的release()方法

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

跳到AQS类中看看这个方法,第一步是调用tryRelease()方法进行判断。
如果成功就获取头节点,判断之后将队列中的头结点唤醒。

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

所以我们就看看ReentrantLock类里的tryRelease()是如何实现的
在子类Sync中

protected final boolean tryRelease(int releases) {
	//获取要解锁后的锁数:c
    int c = getState() - releases;
    //如果当前线程没有持有这把锁,就抛异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    //free表示锁是否空闲
    boolean free = false;
    //如果c==0,表示这把锁没有线程持有了,将互斥锁持有线程设置为null
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    //修改state
    setState(c);
    //返回锁状态
    return free;
}

总结:

  • 加锁过程(非公平):
    CAS去获取锁,成功则获取到锁,否则就进行判断。
    判断时state=0(没有线程持有)已经空闲了,就再次去CAS加锁。如果自己就是持有锁线程,就再重入锁。
    如果都失败了,就进入队列等待。

  • 解锁过程:
    如果解锁后,state=0,锁状态free=true,然后就去队列中唤醒下一个等待的线程。

利用AQS自定义工具类

下面用AQS分别实现一把独占锁和一个一次性门闩。

public class SimpleLock {
    private final Sync sync = new Sync();
    private static int count=0;

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

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

    private class Sync extends AbstractQueuedSynchronizer{
        @Override
        protected boolean tryAcquire(int arg) {
            return compareAndSetState(0,1);
        }

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

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

    public static void main(String[] args) throws InterruptedException {
        SimpleLock lock = new SimpleLock();
        Runnable r = () -> {
            lock.lock();
            try{
                for (int i = 0; i <5000; i++) {
                    count++;
                }
            }finally {
                lock.unlock();
            }
        };
        Thread thread1 = new Thread(r);
        Thread thread2 = new Thread(r);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(count);
    }
}
public class OnceLatch {
    private final Sync sync = new Sync();

    public void await(){
        sync.acquireShared(0);
    }

    public void open(){
        sync.releaseShared(0);
    }

    private class Sync extends AbstractQueuedSynchronizer{
        @Override
        protected int tryAcquireShared(int arg) {
            return (getState() == 1)?1:-1;
        }

        @Override
        protected boolean tryReleaseShared(int arg) {
            setState(1);
            return true;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        OnceLatch onceLatch = new OnceLatch();
        for (int i = 0; i <10; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"等待开门");
                onceLatch.await();
                System.out.println(Thread.currentThread().getName()+":开门啦,冲冲冲");
            }).start();
        }
        TimeUnit.SECONDS.sleep(5);
        System.out.println("开门");
        onceLatch.open();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值