Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。
一、方法摘要
- Semaphore(int permits):构造方法,创建具有给定的许可数和非公平锁的Semaphore。
- Semaphore(int permits, Boolean fair):构造方法,创建具有给定许可数和给定公平设置的Semaphore。
- void acquire():从此信号量获取一个许可,在提供一个许可前将阻塞,否则线程被中断。
- void acquire(int permits):从此信号量获取给定数目的许可,在提供这些许可前阻塞,或者线程已被中断。
- void acquireUninterruptibly():获取许可,无视中断。
- Boolean tryAcquire():仅在调用此信号量时存在一个许可,才从信号量获取许可。
- void release():释放一个许可,将其返回给信号量。
二、使用方法
Semaphore是一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个acquire(),然后再获取许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。
Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。例如,下面的类使用信号量控制对内容池的访问:
public class SemaphoreTest { private static final int THREAD_COUNT = 30; private static ExecutorService threadPool = Executors .newFixedThreadPool(THREAD_COUNT); private static Semaphore s = new Semaphore(10); public static void main(String[] args) { for (int i = 0; i < THREAD_COUNT; i++) { threadPool.execute(new Runnable() { @Override public void run() { try { s.acquire(); //获取许可 System.out.println("save data"); s.release(); //释放许可 } catch (InterruptedException e) { } } }); } threadPool.shutdown(); } }
在代码中,虽然有30个线程在执行,但是只允许10个并发的执行。Semaphore的构造方法Semaphore(int permits) 接受一个整型的数字,表示可用的许可证数量。Semaphore(10)表示允许10个线程获取许可证,也就是最大并发数是10。Semaphore的用法也很简单,首先线程使用Semaphore的acquire()获取一个许可证,使用完之后调用release()归还许可证。还可以用tryAcquire()方法尝试获取许可证。
三、实现原理
结构类图
信号量内含了两个队列同步器,分别是公平的和非公平的。通过队列同步器来控制线程同步。
构造函数
public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits); }
Semaphore内置了两个队列同步器,分别为公平的和非公平的。根据构造参数的不同,决定实现哪一个同步器。
acquire 方法
acquire()方法用来获取许可,这里的许可其实关联到队列同步器AQS的状态state。源码如下:
public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1); //获取共享锁 }
AQS中,该方法的实现为:
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) //尝试获取同步 doAcquireSharedInterruptibly(arg); //尝试失败 则 继续等待 }
该方法为模板方法,需要对tryAcquireShared方法进行重写。对于非公平锁,源码如下:
protected int tryAcquireShared(int acquires) { return nonfairTryAcquireShared(acquires); //调用了父类方法 }
final int nonfairTryAcquireShared(int acquires) { for (;;) { int available = getState(); //获取状态 int remaining = available - acquires; //更新状态 if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; //返回新的状态值 } }
对于公平锁,则需要加上判断,该线程是否为同步队列的头结点才可。acquire本质上是交出同步器的state给线程。当state消耗完时(<=0)就无法再获得共享锁。
release 方法
release方法用来归还信号量。源码如下:
public void release() { sync.releaseShared(1); //调用同步器的释放共享锁 }
该方法调用了同步器的模板方法:
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { //尝试释放锁 doReleaseShared(); return true; } return false; }在Semaphore中,重写了tryReleaseShared方法:
protected final boolean tryReleaseShared(int releases) { for (;;) { int current = getState(); //获取state值 int next = current + releases; //归还信号量 if (next < current) // overflow throw new Error("Maximum permit count exceeded"); if (compareAndSetState(current, next)) //更新state return true; } }