Semaphore([' seməf :(r)])的主要作用是控制线程并发的数量。我们可以将Semaphore想象成景区的一个门卫,这个门卫负责发放景区入园的许可证。
景区为了游客的入园观赏体验,决定最多允许200个有个同时在园内观赏。那么这个门卫在每天开园的时候手中都会有200张许可证,每当一个游客要入园的时候门卫会给游客发放一张许可证,当门卫手中的许可证发完之后再有游客需要入园的话就必须等待。
当游客观赏完毕之后,出园的时候需要将许可证交还到门卫手上。门卫将这些交还的许可证再发等待的游客,这些游客就能顺利入园了。
Semaphore的API简介Semaphore的API使用起来也比较简单,常见的API简介如下:
- Semaphore(int permits):构造方法,创建具有给定许可数的计数信号量并设置为非公平信号量。
- Semaphore(int permits,boolean fair):构造方法,当fair等于true时,创建具有给定许可数的计数信号量并设置为公平信号量。
- void acquire():从此信号量获取一个许可前线程将一直阻塞。相当于一辆车占了一个车位。
- void acquire(int n):从此信号量获取给定数目许可,在提供这些许可前一直将线程阻塞。比如n=2,就相当于一辆车占了两个车位。
- boolean tryAcquire(int permits, long timeout, TimeUnit unit):尝试获取,在给定的时间内没获取到资源超时
- void release():释放一个许可,将其返回给信号量。就如同车开走返回一个车位。
- void release(int n):释放n个许可。
- int availablePermits():当前可用的许可数。
下面给出一个Oracle官方文档中的列子代码:
class Pool { // 可同时访问资源的最大线程数 private static final int MAX_AVAILABLE = 100; private final Semaphore available = new Semaphore(MAX_AVAILABLE, true); // 共享资源 protected Object[] items = new Object[MAX_AVAILABLE]; protected boolean[] used = new boolean[MAX_AVAILABLE]; public Object getItem() throws InterruptedException { available.acquire(); return getNextAvailableItem(); } public void putItem(Object x) { if (markAsUnused(x)) available.release(); } private synchronized Object getNextAvailableItem() { for (int i = 0; i < MAX_AVAILABLE; ++i) { if (!used[i]) { used[i] = true; return items[i]; } } return null; } private synchronized boolean markAsUnused(Object item) { for (int i = 0; i < MAX_AVAILABLE; ++i) { if (item == items[i]) { if (used[i]) { used[i] = false; return true; } else return false; } } return false; } }
items数组可以看成是我们的共享资源,当有线程尝试使用共享资源时,我们要求线程先获得“许可”(调用Semaphore 的acquire方法),这样线程就拥有了权限,否则就需要等待。当使用完资源后,线程需要调用Semaphore 的release方法释放许可。
Semaphore并不能替代synchronized有些书中提到:如果将Semaphore的许可证数量设置成1的话,就能实现synchronized的功能。其实这种说法是不对的。
下面使用Semaphore来控制对一个账户进行并发存钱和取钱的动作,如果Semaphore能实现synchronized的功能的话,账户最后的余额应该还是10000,但代码执行后的结果并不是这样。大家可以执行下面的代码看下结果。
public static final int THREAD_COUNT = 100; public static void main(String[] args) { BankAccount myAccount = new BankAccount("accountOfMG", 10000.00); for (int i = 0; i < THREAD_COUNT; i++) { new Thread(new Runnable() { @Override public void run() { try { int var = new Random().nextInt(100); Thread.sleep(var); } catch (InterruptedException e) { e.printStackTrace(); } double deposit = myAccount.deposit(1000.00); System.out.println(Thread.currentThread().getName() + " balance1:" + deposit); } }).start(); } for (int i = 0; i < THREAD_COUNT; i++) { new Thread(new Runnable() { @Override public void run() { try { int var = new Random().nextInt(100); Thread.sleep(var); } catch (InterruptedException e) { e.printStackTrace(); } double deposit = myAccount.withdraw(1000.00); System.out.println(Thread.currentThread().getName() + " balance2:" + deposit); } }).start(); } } private static class BankAccount { Semaphore semaphore = new Semaphore(1); String accountName; double balance; public BankAccount(String accountName, double balance) { this.accountName = accountName; this.balance = balance; } public double deposit(double amount) { try { semaphore.acquire(); balance = balance + amount; return balance; } catch (Exception e) { throw new RuntimeException("中断..."); } finally { semaphore.release(); } } public double withdraw(double amount) { try { semaphore.acquire(); balance = balance - amount; return balance; } catch (Exception e) { throw new RuntimeException("中断..."); } finally { semaphore.release(); } } }
这里Semaphore并不能实现synchronized的功能的原因是:Semaphore并不能保证共享变量的可见性。
概括总结- Semaphore只能用来做线程同步——控制线程的执行顺序,但是并不能保证线程安全;
- Semaphore主要用来控制线程的并发数量,通常用在限流组件中。
- Semaphore基于AQS机制实现。