使用
Semaphore是计数信号量。Semaphore管理一系列许可证。每个acquire方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个release方法增加一个许可证,这可能会释放一个阻塞的acquire方法。然而,其实并没有实际的许可证这个对象,Semaphore只是维持了一个可获得许可证的数量。
Semaphore经常用于限制获取某种资源的线程数量。下面举个例子,比如说操场上有5个跑道,一个跑道一次只能有一个学生在上面跑步,一旦所有跑道在使用,那么后面的学生就需要等待,直到有一个学生不跑了,下面是这个例子:
/** * 操场,有5个跑道 * Created by Xingfeng on 2016-12-09. */ public class Playground { /** * 跑道类 */ static class Track { private int num; public Track(int num) { this.num = num; } @Override public String toString() { return "Track{" + "num=" + num + '}'; } } private Track[] tracks = { new Track(1), new Track(2), new Track(3), new Track(4), new Track(5)}; private volatile boolean[] used = new boolean[5]; private Semaphore semaphore = new Semaphore(5, true); /** * 获取一个跑道 */ public Track getTrack() throws InterruptedException { semaphore.acquire(1); return getNextAvailableTrack(); } /** * 返回一个跑道 * * @param track */ public void releaseTrack(Track track) { if (makeAsUsed(track)) semaphore.release(1); } /** * 遍历,找到一个没人用的跑道 * * @return */ private Track getNextAvailableTrack() { for (int i = 0; i < used.length; i++) { if (!used[i]) { used[i] = true; return tracks[i]; } } return null; } /** * 返回一个跑道 * * @param track */ private boolean makeAsUsed(Track track) { for (int i = 0; i < used.length; i++) { if (tracks[i] == track) { if (used[i]) { used[i] = false; return true; } else { return false; } } } return false; } } public class SemaphoreDemo { static class Student implements Runnable { private int num; private Playground playground; public Student(int num, Playground playground) { this.num = num; this.playground = playground; } @Override public void run() { try { //获取跑道 Playground.Track track = playground.getTrack(); if (track != null) { System.out.println("学生" + num + "在" + track.toString() + "上跑步"); TimeUnit.SECONDS.sleep(2); System.out.println("学生" + num + "释放" + track.toString()); //释放跑道 playground.releaseTrack(track); } } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { Executor executor = Executors.newCachedThreadPool(); Playground playground = new Playground(); for (int i = 0; i < 100; i++) { executor.execute(new Student(i+1,playground)); } } }
应用场景
Semaphore可以用于做流量控制,特别公用资源有限的应用场景,比如数据库连接。假如有一个需求,要读取几万个文件的数据,因为都是IO密集型任务,我们可以启动几十个线程并发的读取,但是如果读到内存后,还需要存储到数据库中,而数据库的连接数只有10个,这时我们必须控制只有十个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接。这个时候,我们就可以使用Semaphore来做流控
Semaphore有两种模式
Semaphore有两种模式,公平模式和非公平模式。公平模式就是调用acquire的顺序就是获取许可证的顺序,遵循FIFO;而非公平模式是抢占式的,也就是有可能一个新的获取线程恰好在一个许可证释放时得到了这个许可证,而前面还有等待的线程。
Semaphore有两个构造方法,如下:
public Semaphore(int permits) { sync = new NonfairSync(permits); } public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits); }
从上面可以看到两个构造方法,都必须提供许可的数量,第二个构造方法可以指定是公平模式还是非公平模式,默认非公平模式。
Semaphore内部基于AQS的共享模式,所以实现都委托给了Sync类。
总结
Semaphore是信号量,用于管理一组资源。其内部是基于AQS的共享模式,AQS的状态表示许可证的数量,在许可证数量不够时,线程将会被挂起;而一旦有一个线程释放一个资源,那么就有可能重新唤醒等待队列中的线程继续执行。