Java 并发 - Semaphore 信号量(厕所)

Semaphore 信号量

Semaphore作为一种线程间同步机制是非常轻量级的方案.

Semaphore可以控制某个资源可被同时访问的个数,
通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。

比如在Windows下可以设置共享文件的最大客户端访问个数。

Semaphore实现的功能就类似厕所有5个坑,假如有10个人要上厕所,那么同时只能有多少个人去上厕所呢?

同时只能有5个人能够占用,当5个人中 的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用了。

另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会;
这取决于构造Semaphore对象时传入的参数选项。单个信号量的Semaphore对象可以实现互斥锁的功能;

并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。

Semaphore 厕所例子

package com.haleywang.demo;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreDemo {

    //在一些资源有限的场景下很有实用性,比如数据库连接,停车场,厕所,设置共享文件的最大客户端访问个数,...
    public static void main(String[] args) {

        //一个客车拉了22人到一个只有5个坑的厕所.
        ExecutorService exec = Executors.newCachedThreadPool();
        //1.permits: 最大访问线程数, 厕所有5个坑.
        int permits = 5;
        final Semaphore semaphore = new Semaphore(permits);

        for (int i = 0; i < 22; i++) {

            final int NO = i;

            exec.execute(() -> {

                try {
                    //2.从信号量获取一个许可,如果无可用许可前 将一直阻塞等待.
                    semaphore.acquire();

                    System.out.println("in: " + NO);
                    Thread.sleep((long) (Math.random() * 10000));

                    //Returns the current number of permits available in this semaphore.
                    //获取当前信号量可用的许可.
                    System.out.println("availablePermits: " + semaphore.availablePermits());

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("out: " + NO);
                    // release会唤醒一个等待在Semaphore上的一个线程来尝试获得许可
                    // 释放一个许可,别忘了在finally中使用,注意:多次调用该方法,会使信号量的许可数增加,达到动态扩展的效果,
                    // 如:初始permits 为1, 调用了两次release,最大许可会改变为2
                    semaphore.release();
                }
            });
        }

        //所有人都上完WC了
        while (semaphore.availablePermits() < permits) {
            try {
                TimeUnit.SECONDS.sleep(1L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        exec.shutdown();
        System.out.println("availablePermits: " + semaphore.availablePermits());
        System.out.println("end-----------------");
    }

}

Semaphore 原理

在实现上,Semaphore借助了线程同步框架AQS(Java同步框架AbstractQueuedSynchronizer),同样借助了AQS来实现的是java中的可重入锁的实现.

公平模式和非公平模式的Semaphore

abstract static class Sync extends AbstractQueuedSynchronizer

public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

Java并发包基石-AQS, 抽象的队列式的同步器AbstractQueuedSynchronizer.
AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch.