• 多线程分析之Semaphore


    Semaphore分析由来

      网上看了许多讲解Semaphore的,用Semaphore来实现顺序打印字母,但是可能大家都没有清楚具体的原因,所以来给大家分析下为什么可以使用Semaphore来实现顺序打印字母顺序。

    Semaphore源码分析

      先打开JDK8源码中的Semaphore,可以看到Semaphore是通过继承AQS来现实功能(AQS,Doug Lea大神重写并发包的核心,这个默认自己看过哈,其实蛮简单,核心原理:通过模板方法,完成流程调用,让子类实现具体方法,然后实现不同功能)。

    说正事,我们贴出一张Semaphore的层级关系图。

      

    在Semaphore中主要是Sync类实现AbstractQueuedSynchronizer,然后Sync又有两个实现类,分别是FaireSync和NonfairSync,即公平锁和非公平锁。我们来看下Sync中的部分源码:

    /**
    * Synchronization implementation for semaphore. Uses AQS state
    * to represent permits. Subclassed into fair and nonfair
    * versions. (使用AQS的state变量来代表许可,注释这段还是蛮重要)
    */
    abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 1192457210091910933L;

      // 在构造函数中传入许可数
    Sync(int permits) {
    setState(permits);
    }
      
      // 获取许可数量
    final int getPermits() {
    return getState();
    }
      // 非公平锁获取许可方式
    final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
    int available = getState();
    int remaining = available - acquires;
    if (remaining < 0 ||
    compareAndSetState(available, remaining))
    return remaining;
    }
    }
      // 归还许可,更新可用许可数量
    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;
    }
    }
      // 减少许可, 获取时需要减少许可数量
    final void reducePermits(int reductions) {
    for (;;) {
    int current = getState();
    int next = current - reductions;
    if (next > current) // underflow
    throw new Error("Permit count underflow");
    if (compareAndSetState(current, next))
    return;
    }
    }

    }

    在类中,没有特别难的方法,都是都过CAS来进行操作,用AQS中的state来当作许可。好了, 有了这一部分基础,我们可以去看看大家是如何使用Semaphore来实现顺序打印的。还是先为大家贴上代码:

    /**
     * <br>使用信号量顺序打印</br>
     *
     * @author lifacheng
     * @version 1.0
     * @date 2019/6/18 11:08
     * @since 1.0
     */
    public class Thread11 {
        private static Semaphore semaphore1 = new Semaphore(0);
        private static Semaphore semaphore2 = new Semaphore(1);
    
        Thread t1, t2;
    
        int count = 10;
    
        public Thread11() {
            t1 = new Thread() {
                public void run() {
                    try {
                        int i = 0;
                        while(i++ < count) {
                            //获得许可
                            semaphore2.acquire();
                            System.out.print("A");
                            //初始化许可为0,处于阻塞,当release获取许可、
                            semaphore1.release();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            t2 = new Thread() {
                public void run() {
                    try {
                        int i = 0;
                        while(i++ < count) {
                            semaphore1.acquire();
                            System.out.print("B");
                            semaphore2.release();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        }
    
        public void run() {
            t1.start();
            t2.start();
        }
    
        public static void main(String args[]) throws Exception {
            Thread11 t = new Thread11();
            t.run();
        }
    }

    在代码一开始,初始化了两个信号量,分别为:semaphore1,semaphore2,semaphore1的许可书为0个,semaphore2的许可书为1个。

    acquire()源码分析

      在线程t1的run()方法中,semaphore2执行了acquire()方法,我们打开源码看看这段逻辑是什么,

      

      代码调用了sync中的acquireSharedInterruptibly()方法,此时我们要注意,传入的参数是1个(这个很重要)。我们接着往下找,找到这个是AQS中实现的方法。

      

       我们找到具体的实现类中的方法,即tryAcquireShare(arg)这个在Semaphore中实现的方法。(在tryAcquireShare(arg)<0时,会进去doAcquireSharedInterruptibly()方法中,当获取小于0会处于阻塞状态)

      

      我们可以看到有Semaphore有两个实现类,分别是公平和非公平两种实现,我们点进去看看到底有何不同。

      公平:

      非公平:

      我们看到,公平就比非公平多了一个判断,这个判断就是判断是否是头节点(就不点进去看了哈)。

      至此,我们可以看到,acquire()方法具体实现就是获取了一个许可。我们接着看代码,然后输出字符A,semaphore1执行了release()方法。

    release()方法分析

      我们点进release方法查看,

      

      按照和acquire()的相同的逻辑,最后找到如上这段代码,即在执行release()方法时,会增加1个许可。

    代码逻辑

      自此我们知道代码的逻辑了,来具体分析下代码。

      当线程t1和t2同时开始运行,这个t1开始执行获取到许可,然后输出A,这是就算t2拿到CPU执行权,由于初始化许可数为0,这时acquire()方法获取不到许可,处于阻塞状态。只有当t1中的semaphore1执行了release()方法时,才会增加一个许可,t2获取到CPU执行权后才会执行。假设此时t1又获取到CPU执行权,但是由于只有一个许可,开始获取过许可,再此获取会失败,也会处于阻塞状态,只有t2线程中semaphore2执行了release()方法才会增加一个许可,然后t1才会再次获取成功并执行。

      代码充分使用了许可数量来控制线程的执行,当线程执行时,相互唤醒,增加许可数量。有点像wait和notify的概念,但是更高级,我们可以增加semaphore来控制多个线程执行顺序。 这下次就分析清楚了,为什么初始化0个许可数的semaphore仍然可以用来控制。可以说相当神奇。我们也对AQS的强大有了一个小小的了解。

    总结

      AQS的强大一时半会说不清楚,希望大家多看看源码,结合百家之所长,来提升自己。

  • 相关阅读:
    windows 共享文件夹 给 mac
    给mac配置adb 路径
    关于android 加载https网页的问题
    http tcp udp ip 间的关系
    手机服务器微架构设计和实现专题
    添加ssh key
    本人对于线程池的理解和实践
    使用Android Butterknife
    记一次失败的笔试(华为研发工程师-汽水瓶笔试题)
    简易坦克大战python版
  • 原文地址:https://www.cnblogs.com/lifacheng/p/11110530.html
Copyright © 2020-2023  润新知