Semaphore信号量

starlin 695 2018-06-04

信号量Semaphore是一个控制访问多个共享资源的计数器,和CountDownLatch一样,其本质上是一个“共享锁”。

Semaphore介绍

Semaphore在API是这么介绍的:

一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前阻塞每一个acquire(),然后在获得该许可。每个release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。

Semaphore通常用于控制当前访问某些资源的线程个数,并提供了同步机制
举个简单的例子来阐述Semaphore:
假如有这么一个场景,一个厕所有5个坑位,刚开始全部空着,来了3个人,占了3个坑,然后又来了3个人,这个时候由于只有2个坑位,所以只能2个人,另外一个人只能候着,直到坑位空出来。包括后面来的人,也必须在外面候着。当坑位空着了,另外等待的人可以随机进去还是按照先来后到的顺序进去,这取决于构造Semaphore对象传入的参数选项。

从程序的角度来看,厕所坑位就相当于信号量Semaphore,其中许可数为5,上厕所的人就相当于线程,当进来一个人许可数就减1,当没有了坑位(许可就为0了)

信号量是一个非负整数(大于等于1),当一个线程访问共享资源时,它必须先获取Semaphore,当Semaphore>0时,获取该资源并使Semaphore-1,若Semaphore为0,则表示全部的共享资源已经被其他线程占用,必须等待其他线程释放资源。

Semaphore分析

从Semaphore源码来看,其内部结构包含了FairSync(公平锁)和NonfairSync(非公平锁),基础内部类Sync,其中Sync继承AQS(AQS很重要)。

构造函数

Semaphore提供2个构造函数:

  1. Semaphore(int permits),创建具有给定的许可数和非公平的公平设置的 Semaphore
    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }
  1. Semaphore(int permits, boolean fair),创建具有给定的许可数和给定的公平设置的 Semaphore
    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

Semaphore默认选择非公平锁,当信号量Semaphore=1时,它可以当做互斥锁使用。其中0,1就相当于它的状态,当为1时表示其他线程可以获取;当为0时,即其他线程必须等待。

信号量获取

Semaphore提供了acquire()方法来获取一个许可

    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

内部调用AQS的acquireSharedInterruptibly(int arg),该方法以共享模式获取同步状态:

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

信号量释放

Semaphore提供release()来释放许可

    public void release(int permits) {
        if (permits < 0) throw new IllegalArgumentException();
        sync.releaseShared(permits);
    }

内部调用的AQS中的releaseShared():

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

代码示例

就拿上面的厕所坑位来举例:

public class SemaphoreDemo {
    public static void main(String[] args) throws Exception{
        ExecutorService service = Executors.newCachedThreadPool();
        //5个坑位
        final Semaphore semaphore = new Semaphore(5);
        //10个人排队
        for (int i = 0; i < 10; i++) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        semaphore.acquire();
                        System.out.println("线程" + Thread.currentThread().getName() + "进入,当前已有" + (5 - semaphore.availablePermits()) + "个并发");
                        Thread.sleep((long) (Math.random()*10000));
                        System.out.println("线程" + Thread.currentThread().getName() + "即将离开");
                        semaphore.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            };
            service.execute(runnable);
        }
    }
}

运行结果如下:

线程pool-1-thread-2进入,当前已有2个并发
线程pool-1-thread-1进入,当前已有2个并发
线程pool-1-thread-3进入,当前已有3个并发
线程pool-1-thread-4进入,当前已有4个并发
线程pool-1-thread-5进入,当前已有5个并发
线程pool-1-thread-2即将离开
线程pool-1-thread-6进入,当前已有5个并发
线程pool-1-thread-3即将离开
线程pool-1-thread-7进入,当前已有5个并发
线程pool-1-thread-4即将离开
线程pool-1-thread-8进入,当前已有5个并发
线程pool-1-thread-5即将离开
线程pool-1-thread-9进入,当前已有5个并发
线程pool-1-thread-9即将离开
线程pool-1-thread-10进入,当前已有5个并发
线程pool-1-thread-7即将离开
线程pool-1-thread-8即将离开
线程pool-1-thread-10即将离开
线程pool-1-thread-1即将离开
线程pool-1-thread-6即将离开

# java并发