Java并发——闭锁CountDownLatch、信号量Semaphore类源码解析

版权声明:个人博客:blog.suyeq.com 欢迎访问 https://blog.csdn.net/hackersuye/article/details/84990209

    写在前面,阅读这篇文章,需要这些知识:
    Java并发——Thread类解析、线程初探
    Java并发——CAS原子操作
    Java并发——AQS框架详解

CountDownLatch

    CountDownLatch类是AQS框架闭锁机制的一种实现。这是种什么机制呢?假如有以下场景,有三个线程需要并发的进行,但是线程A与B需要在线程C执行完成后才执行,也就是说,线程C完成了后,挡住线程A与B的“锁”才会打开。它的应用场景如下:

public class Test {
    public static void main (String args[]) throws InterruptedException {
        CountDownLatch count=new CountDownLatch(1);

        Thread thread2=new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    count.await();
                    System.out.println("拉姆执行了啦");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread1=new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    count.await();
                    System.out.println("蕾姆执行了啦");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread3=new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("艾米莉亚执行了");
                count.countDown();
            }
        });
        thread2.start();
        thread1.start();
        thread3.start();
    }
}
//输出:
//艾米莉亚执行了
//拉姆执行了啦
//蕾姆执行了啦

    从代码中可以看出,即便是thread3线程最后才开始而且等待了3秒钟,线程thread1thread2也在等在thread3线程执行完才执行。CountDownLatch类的内部定义了Sync同步器继承自AQS框架,可以自定义资源state的大小:

public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

    它的请求获取资源的方法如下:

public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
}
    
protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

    从它的请求资源的方法可以看出,CountDownLatch类是将AQS框架中的state值作为一个计数器,当该值不为0的时候,代表着线程请求资源失败,需要加入AQS的同步队列中等待state为0的时候继续获得资源执行。同时state的大小也代表着可以让多少个线程进入同步队列等待state为0的时候继续运行。对AQS框架不熟的同学可以看看这篇文章:Java并发——AQS框架详解。而它的释放的资源的代码如下:

public void countDown() {
		//释放1个资源
        sync.releaseShared(1);
}

protected boolean tryReleaseShared(int releases) {
            // 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;
            }
        }

    直接将state的值减一,减到零代表其它因获取资源失败的线程可以重新得到运行,利用这个特性,我们可以用来实现闭锁的机制。

Semaphore

    SemaphoreAQS框架信号量机制的一种实现。它首先在创建时会定义一定的资源量,每个线程如果能从Semaphore里得到足够的资源,那么该线程就会得到执行,而如果得不到足够的资源,那么AQS会把线程置于同步队列中,来等待前面获得资源的线程执行完毕释放资源。假如有两两个资源,线程A得到了一个在运行,线程B得到了一个运行,但是线程C因资源不足就会进入AQS的同步队列陷入等待线程A或者B释放资源,它才能得到资源进行运行,如下代码:

public class Test {
    public static void main (String args[]) throws InterruptedException {
        CountDownLatch count=new CountDownLatch(1);
        Semaphore semaphore=new Semaphore(2);
        Lock lock=new ReentrantLock();

        Thread thread2=new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                   semaphore.acquire();
                    System.out.println("拉姆执行中");
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("拉姆执行完毕啦");
                    //semaphore.release();
                }

            }
        });
        Thread thread1=new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //count.await();
                    semaphore.acquire();
                    System.out.println("蕾姆执行中");
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("蕾姆执行完毕啦");
                    //semaphore.release();
                }
            }
        });
        Thread thread3=new Thread(new Runnable() {
            @Override
            public void run() {
                try {  
                    semaphore.acquire();
                    System.out.println("艾米莉亚开始执行了");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //count.countDown();
            }
        });

        thread2.start();
        thread1.start();
        thread3.start();
    }
}
//输出:
//拉姆执行中
//蕾姆执行中
//拉姆执行完毕啦
//蕾姆执行完毕啦

    如果注释的地方没有去掉,也就是没有释放资源的话,那么thread3会一直陷入等待资源的情况,而不会执行下面的代码。同时,Semaphore类也分公平非公平两种情况,而默认的是非公平的:

 public Semaphore(int permits) {
        sync = new NonfairSync(permits);
}

    以非公平的Semaphore请求资源源码为示例:

final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                //减去线程请求的资源量
                int remaining = available - acquires;
                //如果资源量小于0或CAS操作成功了
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

    可以看得出来,用循环来重试失败的CAS原子操作,以此来保证state的改变一定能成功,也就是说在AQS中的state表示的是资源量的大小。当资源量小于线程请求的大小时,线程会进入AQS得到同步队列,来等待资源的释放。公平的请求方法是在这个基础上加了一个判断,如果判断同步队列中有线程在等待,那么直接将新来的线程加入到队尾,保证处于同步队列中的线程先来先执行。而其释放方法如下:

protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState();
                int next = current + releases;
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))
                    return true;
            }
        }

    增加state的大小,这个大小等于线程释放的资源量。此外Semaphore还定义了可轮旋的、定时的、可以中断异常的三个请求方法:

public boolean tryAcquire();
public boolean tryAcquire(long timeout, TimeUnit unit);
public void acquireUninterruptibly();

    默认的acquire方法调用的是acquireUninterruptibly方法,默认可以中断异常。

猜你喜欢

转载自blog.csdn.net/hackersuye/article/details/84990209