Skip to main content

信号量Semaphore

huhxAbout 3 minjavaConcurrency-ToolkitConcurrency

正常的锁不管是内部锁synchronized还是重入锁ReentrantLock在任意时刻只允许一个线程访问临界资源,Java中有没有可以让多个任务同时这个临界资源呢?接下来就让我们来看下线程同步的辅助类Semaphore

Semaphore使用

信号量Semaphore为多线程协作提供了更为强大的控制方法,它可以指定多个线程同时访问某个资源。Semaphore有以下两个构造函数:

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

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

其中需要指定许可证(permits)的大小,也就是同时可以申请多少许可证。不带fair参数的信号量是非公平的,即多个线程同时去获取许可,哪个线程会拿到是不确定的。而当fair为true时,信号量是公平的,即多个线程同时去获取许可,是按照FIFO队列的规则来选的线程。

Semaphore中主要的方法有:

public void acquire()
public void acquireUninterruptibly()
public boolean tryAcquire()
public boolean tryAcquire(long timeout, TimeUnit unit) 
public void release()
  • acquire: 获得一个准入的许可。若无法获得,则线程会等待直到有其它线程释放一个许可或者当前线程被中断
  • acquireUninterruptibly: 和acquire方法类似,区别在于它不响应中断
  • tryAcquire: 尝试获得一个许可,如果成功返回true,失败则返回false,它不会进行等待而是立即返回。带参数的tryAcquire区别在于它会等待指定的时间
  • release: 用于资源访问结束后释放一个许可

当然上述方法都有带permits额外参数的重载方法,表示一次性可以操作多个许可,这比循环去获取或者释放的方式更加的高效。

public boolean acquire(int permits)
...
public void release(int permits)

下面我们就举个银行业务办理的例子来说明Semaphore的用法:

public class SemaphoreTest {
    final static Semaphore availableWindow = new Semaphore(2);

    public static void main(String[] args) {
        var runnable = new Runnable() {
            int count = 1;

            @Override
            public void run() {
                int time = (int) (Math.random() * 10 + 3);
                int num = count++;
                try {
                    availableWindow.acquire();
                    System.out.println("正在为第【" + num + "】个客户办理业务,需要时间:" + time + "s!");
                    Thread.sleep(time * 1000L);
                    if (availableWindow.hasQueuedThreads()) {
                        System.out.println("第【" + num + "】个客户已办理完业务,有请下一位!");
                    } else {
                        System.out.println("第【" + num + "】个客户已办理完业务,没有客户了,休息中!");
                    }
                    availableWindow.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        for (int i = 1; i < 5; i++) {
            new Thread(runnable).start();
        }
    }
}












 


 




 











输出不确定,某次的结果如下:

正在为第【1】个客户办理业务,需要时间:3s!
正在为第【2】个客户办理业务,需要时间:11s!
第【1】个客户已办理完业务,有请下一位!
正在为第【4】个客户办理业务,需要时间:7s!
第【4】个客户已办理完业务,有请下一位!
正在为第【3】个客户办理业务,需要时间:5s!
第【2】个客户已办理完业务,没有客户了,休息中!
第【3】个客户已办理完业务,没有客户了,休息中!

这里我们创建五个线程来模拟五个用户去银行办理业务,其中Semaphore的许可个数设置为2,表示银行业务办理窗口的数量,也就是说同时办理业务的人数最大是2。

Semaphore的hasQueuedThreads方法查询是否有线程正在等待获取许可。

从上述的输出结果得知:银行有两个窗口,一开始客户1和客户2得到了办理的机会,3秒之后客户1办理完释放了窗口。于是客户4得到机会去办理业务,由于客户4业务比较简单只花了7秒钟就完成了。于是这个窗口被释放,客户3得到了办理的机会。最后客户2和客户3办理完业务。

Semaphore分析

FAQ

总结

参考