Semaphore 计数信号量

转载: https://blog.csdn.net/hanchao5272/article/details/79780045

1.Semaphore 简介

Semaphore 是JDK1.5的 java.util.concurrent 并发包中提供的一个并发工具类。

所谓 Semaphore 即 信号量 的意思。
这个叫法并不能很好地表示它的作用,更形象的说法应该是 许可证管理器 。

Semaphore 是一个计数信号量。

  • 从概念上将,Semaphore 包含一组许可证。
  • 如果有需要的话,每个 acquire() 方法都会阻塞,直到获取一个可用的许可证。
  • 每个 release() 方法都会释放持有许可证的线程,并且归还 Semaphore 一个可用的许可证。
  • 然而,实际上并没有真实的许可证对象供线程使用,Semaphore 只是对可用的数量进行管理维护。

2.Semaphore 方法说明

2.1、构造函数

Semaphore(permits)   
Semaphore(permits,fair)

permits:初始化许可证数量
fair:是否公平模式

2.2、方法

  • 1、 isFair()
    是否公平模式 FIFO

  • 2、availablePermits()
    获取当前可用的许可证数量

  • 3、acquire()acquire(permits)
    当前线程尝试去阻塞的获取 permits 个许可证。( permits >= 1)
    此过程是阻塞的,它会一直等待许可证,直到发生以下任意一件事:

    • 当前线程获取了n个可用的许可证,则会停止等待,继续执行。
    • 当前线程被中断,则会抛出 InterruptedException 异常,并停止等待,继续执行。
  • 4、acquierUninterruptibly()acquireUninterruptibly(permits)
    当前线程尝试去阻塞的获取 permits 个许可证。 ( permits >= 1)
    此过程是阻塞的,它会一直等待许可证,直到发生以下任意一件事:

    • 当前线程获取了n个可用的许可证,则会停止等待,继续执行。
  • 5、tryAcquire()tryAcquire(permits)
    当前线程尝试去获取 permits个许可证。
    此过程是非阻塞的,它只是在方法调用时进行一次尝试。

    • 如果当前线程获取了 permits 个可用的许可证,则会停止等待,继续执行,并返回 true。
    • 如果当前线程没有获得 permits 个许可证,也会停止等待,继续执行,并返回 false 。
  • 6、tryAcquire(timeout,TimeUnit)tryAcquire(permits,timeout,TimeUnit)
    当前线程在限定时间内,阻塞的尝试去获取 permits 个许可证。
    此过程是阻塞的,它会一直等待许可证,直到发生以下任意一件事:

    • 当前线程获取了可用的 permits 个许可证,则会停止等待,继续执行,并返回true。
    • 当前线程等待时间 timeout 超时,则会停止等待,继续执行,并返回false。
    • 当前线程在 timeout 时间内被中断,则会抛出 InterruptedException 一次,并停止等待,继续执行。
  • 7、release()release(permits)
    当前线程释放 permits 个可用的许可证 。( permits >= 1)

  • 8、drainPermits()
    当前线程获得剩余的所有可用许可证

  • 9、hasQueuedThreads()
    判断当前 Semaphore 对象上是否存在正在等待许可证的线程

  • 10、getQueueLength()
    获取当前 Semaphore 对象 上是正在等待许可证的线程数量

3、Semaphore方法练习

练习目的:熟悉Semaphore的各类方法的用法。

3.1、实例代码:

//new Semaphore(permits):初始化许可证数量的构造函数
Semaphore semaphore = new Semaphore(5);

//new Semaphore(permits,fair):初始化许可证数量和是否公平模式的构造函数
semaphore = new Semaphore(5, true);

//isFair():是否公平模式FIFO
System.out.println("是否公平FIFO:" + semaphore.isFair());

//availablePermits():获取当前可用的许可证数量
System.out.println("获取当前可用的许可证数量:开始---" + semaphore.availablePermits());

//acquire():获取1个许可证
//---此线程会一直阻塞,直到获取这个许可证,或者被中断(抛出InterruptedException异常)。
semaphore.acquire();
System.out.println("获取当前可用的许可证数量:acquire 1 个---" + semaphore.availablePermits());

//release():释放1个许可证
semaphore.release();
System.out.println("获取当前可用的许可证数量:release 1 个---" + semaphore.availablePermits());

//acquire(permits):获取n个许可证
//---此线程会一直阻塞,直到获取全部n个许可证,或者被中断(抛出InterruptedException异常)。
semaphore.acquire(2);
System.out.println("获取当前可用的许可证数量:acquire 2 个---" + semaphore.availablePermits());

//release(permits):释放n个许可证
semaphore.release(2);
System.out.println("获取当前可用的许可证数量:release 1 个---" + semaphore.availablePermits());

//hasQueuedThreads():是否有正在等待许可证的线程
System.out.println("是否有正在等待许可证的线程:" + semaphore.hasQueuedThreads());

//getQueueLength():正在等待许可证的队列长度(线程数量)
System.out.println("正在等待许可证的队列长度(线程数量):" + semaphore.getQueueLength());

Thread.sleep(10);
System.out.println();
//定义final的信号量
Semaphore finalSemaphore = semaphore;
new Thread(() -> {
    //drainPermits():获取剩余的所有的许可证
    int permits = finalSemaphore.drainPermits();
    System.out.println(Thread.currentThread().getName() + "获取了剩余的全部" + permits + "个许可证.");
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //释放所有的许可证
    finalSemaphore.release(permits);
    System.out.println(Thread.currentThread().getName() + "释放了" + permits + "个许可证.");
}).start();

Thread.sleep(10);
new Thread(() -> {
    try {
        //有一个线程正在等待获取1个许可证
        finalSemaphore.acquire();
        System.out.println(Thread.currentThread().getName() + "获取了1个许可证.");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //释放1个许可证
    finalSemaphore.release();
    System.out.println(Thread.currentThread().getName() + "释放了1个许可证.");

}).start();
Thread.sleep(10);
System.out.println();
System.out.println("获取当前可用的许可证数量:drain 剩余的---" + finalSemaphore.availablePermits());
System.out.println("是否有正在等待许可证的线程:" + finalSemaphore.hasQueuedThreads());
System.out.println("正在等待许可证的队列长度(线程数量):" + finalSemaphore.getQueueLength());
System.out.println();

Thread.sleep(10);
new Thread(() -> {
    try {
        //有一个线程正在等待获取2个许可证
        finalSemaphore.acquire(2);
        System.out.println(Thread.currentThread().getName() + "获取了2个许可证.");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //释放两个许可证
    finalSemaphore.release(2);
    System.out.println(Thread.currentThread().getName() + "释放了2个许可证.");
}).start();
Thread.sleep(10);
System.out.println();
System.out.println("获取当前可用的许可证数量:drain 剩余的---" + finalSemaphore.availablePermits());
System.out.println("是否有正在等待许可证的线程:" + finalSemaphore.hasQueuedThreads());
System.out.println("正在等待许可证的队列长度(线程数量):" + finalSemaphore.getQueueLength());
System.out.println();

Thread.sleep(5000);
System.out.println();
System.out.println("获取当前可用的许可证数量:---" + finalSemaphore.availablePermits());
System.out.println("是否有正在等待许可证的线程:" + finalSemaphore.hasQueuedThreads());
System.out.println("正在等待许可证的队列长度(线程数量):" + finalSemaphore.getQueueLength());

3.2、运行结果:

是否公平FIFO:true
获取当前可用的许可证数量:开始---5
获取当前可用的许可证数量:acquire 1 个---4
获取当前可用的许可证数量:release 1 个---5
获取当前可用的许可证数量:acquire 2 个---3
获取当前可用的许可证数量:release 1 个---5
是否有正在等待许可证的线程:false
正在等待许可证的队列长度(线程数量):0

Thread-0获取了剩余的全部5个许可证.

获取当前可用的许可证数量:drain 剩余的---0
是否有正在等待许可证的线程:true
正在等待许可证的队列长度(线程数量):1


获取当前可用的许可证数量:drain 剩余的---0
是否有正在等待许可证的线程:true
正在等待许可证的队列长度(线程数量):2

Thread-0释放了5个许可证.
Thread-2获取了2个许可证.
Thread-1获取了1个许可证.
Thread-1释放了1个许可证.
Thread-2释放了2个许可证.

获取当前可用的许可证数量:---5
是否有正在等待许可证的线程:false
正在等待许可证的队列长度(线程数量):0

4、Semaphore应用场景-实例

Semaphore 经常用于限制获取某种资源的线程数量。

4.1、场景说明:

  1. 模拟学校食堂的窗口打饭过程
  2. 学校食堂有2个打饭窗口
  3. 学校中午有20个学生 按次序 排队打饭
  4. 每个人打饭时耗费时间不一样
  5. 有的学生耐心很好,他们会一直等待直到打到饭
  6. 有的学生耐心不好,他们等待时间超过了心里预期,就不再排队,而是回宿舍吃泡面了
  7. 有的学生耐心很好,但是突然接到通知,说是全班聚餐,所以也不用再排队,而是去吃大餐了

4.2、重点分析

  1. 食堂有2个打饭窗口 :需要定义一个 permits=2 的 Semaphore 对象。
  2. 学生 按次序 排队打饭 :此 Semaphore 对象是公平的。
  3. 有20个学生 :定义20个学生线程。
  4. 打到饭的学生 :调用了 acquireUninterruptibly() 方法,无法被中断
  5. 吃泡面的学生 :调用了 tryAcquire(timeout,TimeUnit) 方法,并且等待时间超时了
  6. 吃大餐的学生 :调用了 acquire() 方法,并且被中断了

4.3、实例代码:

4.3.1、定义2个窗口的食堂

/**
 * 打饭窗口
 * 2:   2个打饭窗口
 * true:公平队列-FIFO
 */
static Semaphore semaphore = new Semaphore(2, true);

4.3.2、定义打饭学生

/**
 * 打饭学生
 * 
 **/
static class Student implements Runnable {
    private static final Logger LOGGER = Logger.getLogger(Student.class);
    //学生姓名
    private String name;
    //打饭许可
    private Semaphore semaphore;
    
    /**
    *<pre>
     * 打饭方式
     * 0    一直等待直到打到饭
     * 1    等了一会不耐烦了,回宿舍吃泡面了
     * 2    打饭中途被其他同学叫走了,不再等待
     * </pre>
     */
    private int type;

    public Student(String name, Semaphore semaphore, int type) {
        this.name = name;
        this.semaphore = semaphore;
        this.type = type;
    }

    /**
     * 打饭      
     **/
    @Override
    public void run() {
        //根据打饭情形分别进行不同的处理
        switch (type) {
            //打饭时间
            //这个学生很有耐心,它会一直排队直到打到饭
            case 0:
                //排队
                semaphore.acquireUninterruptibly();
                //进行打饭
                try {
                    Thread.sleep(RandomUtils.nextLong(1000, 3000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //将打饭机会让后后面的同学
                semaphore.release();
                //打到了饭
                LOGGER.info(name + " 终于打到了饭.");
                break;

            //这个学生没有耐心,等了1000毫秒没打到饭,就回宿舍泡面了
            case 1:
                //排队
                try {
                    //如果等待超时,则不再等待,回宿舍吃泡面
                    if (semaphore.tryAcquire(RandomUtils.nextInt(6000, 16000), TimeUnit.MILLISECONDS)) {
                        //进行打饭
                        try {
                            Thread.sleep(RandomUtils.nextLong(1000, 3000));
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //将打饭机会让后后面的同学
                        semaphore.release();
                        //打到了饭
                        LOGGER.info(name + " 终于打到了饭.");
                    } else {
                        //回宿舍吃泡面
                        LOGGER.info(name + " 回宿舍吃泡面.");
                    }
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                }
                break;

            //这个学生也很有耐心,但是他们班突然宣布聚餐,它只能放弃打饭了
            case 2:
                //排队
                try {
                    semaphore.acquire();
                    //进行打饭
                    try {
                        Thread.sleep(RandomUtils.nextLong(1000, 3000));
                    } catch (InterruptedException e) {
                        //e.printStackTrace();
                    }
                    //将打饭机会让后后面的同学
                    semaphore.release();
                    //打到了饭
                    LOGGER.info(name + " 终于打到了饭.");
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                    //被叫去聚餐,不再打饭
                    LOGGER.info(name + " 全部聚餐,不再打饭.");
                }
                break;
            default:
                break;
        }
    }
}

4.3.3、 编写食堂打饭过程:

/**
 * 食堂打饭
 **/
public static void main(String[] args) throws InterruptedException {
    //101班的学生
    Thread[] students101 = new Thread[5];
    for (int i = 0; i < 20; i++) {
        //前10个同学都在耐心的等待打饭
        if (i < 10) {
            new Thread(new Student("打饭学生" + i, SemaphoreDemo.semaphore, 0)).start();
        } else if (i >= 10 && i < 15) {//这5个学生没有耐心打饭,只会等1000毫秒
            new Thread(new Student("泡面学生" + i, SemaphoreDemo.semaphore, 1)).start();
        } else {//这5个学生没有耐心打饭
            students101[i - 15] = new Thread(new Student("聚餐学生" + i, SemaphoreDemo.semaphore, 2));
            students101[i - 15].start();
        }
    }
    //
    Thread.sleep(5000);
    for (int i = 0; i < 5; i++) {
        students101[i].interrupt();
    }
}

4.3.4、运行结果:

2018-04-01 21:13:16 INFO - 打饭学生1 终于打到了饭.
2018-04-01 21:13:16 INFO - 打饭学生0 终于打到了饭.
2018-04-01 21:13:18 INFO - 打饭学生2 终于打到了饭.
2018-04-01 21:13:18 INFO - 打饭学生3 终于打到了饭.
2018-04-01 21:13:19 INFO - 聚餐学生15 全部聚餐,不再打饭.
2018-04-01 21:13:19 INFO - 聚餐学生19 全部聚餐,不再打饭.
2018-04-01 21:13:19 INFO - 聚餐学生17 全部聚餐,不再打饭.
2018-04-01 21:13:19 INFO - 聚餐学生18 全部聚餐,不再打饭.
2018-04-01 21:13:19 INFO - 聚餐学生16 全部聚餐,不再打饭.
2018-04-01 21:13:19 INFO - 打饭学生4 终于打到了饭.
2018-04-01 21:13:20 INFO - 打饭学生5 终于打到了饭.
2018-04-01 21:13:21 INFO - 泡面学生13 回宿舍吃泡面.
2018-04-01 21:13:21 INFO - 泡面学生11 回宿舍吃泡面.
2018-04-01 21:13:21 INFO - 打饭学生7 终于打到了饭.
2018-04-01 21:13:22 INFO - 打饭学生6 终于打到了饭.
2018-04-01 21:13:23 INFO - 打饭学生9 终于打到了饭.
2018-04-01 21:13:24 INFO - 打饭学生8 终于打到了饭.
2018-04-01 21:13:24 INFO - 泡面学生10 终于打到了饭.
2018-04-01 21:13:26 INFO - 泡面学生14 终于打到了饭.
2018-04-01 21:13:26 INFO - 泡面学生12 终于打到了饭.

猜你喜欢

转载自blog.csdn.net/xiaojin21cen/article/details/87376212