Semaphore当前在多线程环境下被扩放使用,操作系统的信号量是个很重要的概念,在进程控制方面都有应用。Java 并发库 的Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。比如在Windows下可以设置共享文件的最大客户端访问个数。
Semaphore实现的功能就类似厕所有5个坑,假如有10个人要上厕所,那么同时只能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中 的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用了。另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。
Semaphore维护了当前访问的个数,提供同步机制,控制同时访问的个数。在数据结构中链表可以保存“无限”的节点,用Semaphore可以实现有限大小的链表。另外重入锁 ReentrantLock 也可以实现该功能,但实现上要复杂些。
下面的Demo中申明了一个只有5个许可的Semaphore,而有20个线程要访问这个资源,通过acquire()和release()获取和释放访问许可。
package com.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class TestSemaphore {
public static void main(String[] args) {
// 线程池
ExecutorService exec = Executors.newCachedThreadPool();
// 只能5个线程同时访问
final Semaphore semp = new Semaphore(5);
// 模拟20个客户端访问
for (int index = 0; index < 20; index++) {
final int NO = index;
Runnable run = new Runnable() {
public void run() {
try {
// 获取许可
semp.acquire();
System.out.println("Accessing: " + NO);
Thread.sleep((long) (Math.random() * 10000));
// 访问完后,释放
semp.release();
System.out.println("-----------------"+semp.availablePermits());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
exec.execute(run);
}
// 退出线程池
exec.shutdown();
}
}
执行结果如下:
Accessing: 0
Accessing: 1
Accessing: 3
Accessing: 4
Accessing: 2
-----------------0
Accessing: 6
-----------------1
Accessing: 7
-----------------1
Accessing: 8
-----------------1
Accessing: 10
-----------------1
Accessing: 9
-----------------1
Accessing: 5
-----------------1
Accessing: 12
-----------------1
Accessing: 11
-----------------1
Accessing: 13
-----------------1
Accessing: 14
-----------------1
Accessing: 15
-----------------1
Accessing: 16
-----------------1
Accessing: 17
-----------------1
Accessing: 18
-----------------1
Accessing: 19
一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。拿到信号量的线程可以进入代码,否则就等待。通过acquire()和release()获取和释放访问许可。
public void acquire() throws InterruptedException
如果没有可用的许可,则在发生以下两种情况之一前,禁止将当前线程用于线程安排目的并使其处于休眠状态:
release()
方法,并且当前线程是下一个要被分配许可的线程;或者如果当前线程:
中断
。 则抛出 InterruptedException
,并且清除当前线程的已中断状态。
InterruptedException
- 如果当前线程被中断
public void release()
不要求释放许可的线程必须通过调用 acquire()
来获取许可。通过应用程序中的编程约定来建立信号量的正确用法。
Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。例如,下面的类使用信号量控制对内容池的访问: class Pool { private static final int MAX_AVAILABLE = 100; private final Semaphore available = new Semaphore(MAX_AVAILABLE, true); public Object getItem() throws InterruptedException { available.acquire(); return getNextAvailableItem(); } public void putItem(Object x) { if (markAsUnused(x)) available.release(); } // Not a particularly efficient data structure; just for demo protected Object[] items = ... whatever kinds of items being managed protected boolean[] used = new boolean[MAX_AVAILABLE]; protected synchronized Object getNextAvailableItem() { for (int i = 0; i < MAX_AVAILABLE; ++i) { if (!used[i]) { used[i] = true; return items[i]; } } return null; // not reached } protected 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; } } 获得一项前,每个线程必须从信号量获取许可,从而保证可以使用该项。该线程结束后,将项返回到池中并将许可返回到该信号量,从而允许其他线程获取该项。注意,调用 acquire() 时无法保持同步锁,因为这会阻止将项返回到池中。信号量封装所需的同步,以限制对池的访问,这同维持该池本身一致性所需的同步是分开的。
package cn.heima.test; import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; public class MySemaphoreDemo { /**信号量好比一家餐馆有n个座位,假如多个线程好比一群人冲进餐馆, * 当然是先占到座位的先开始有饭吃(站着吃饭的不考虑) * 座位有限没占到座位的之后等待(阻塞),等别人吃完了离开(释放信号) * 一有空位,等会的人再去抢,抢上就看运气了 */ public static void show(Object o) { System.out.println(o); } public static void main(String[] args) { // TODO Auto-generated method stub //创建一个固定数量为10的线程池 ExecutorService services=Executors.newFixedThreadPool(10); //创建一个信号量为3的Semaphore对象 final Semaphore sp=new Semaphore(3); for(int i=0;i<10;i++) services.execute(new Runnable() { public void run() { //判断还有信号量可以获取吗 if(sp.availablePermits()<=0) show("没有座位了"+Thread.currentThread().getName()+"请等候"); try { //获得信号,则执行线程,信号没了,暂时阻塞 sp.acquire(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } show(Thread.currentThread().getName()+"进门,占到座位!开始吃饭"); try { Thread.sleep(new Random().nextInt(5000)); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } show(Thread.currentThread().getName()+"吃完了,走了,又有空座位 了"); //线程运行结束,信号释放 sp.release(); } }); } } 运行结果如下: pool-1-thread-1进门,占到座位!开始吃饭 pool-1-thread-2进门,占到座位!开始吃饭 pool-1-thread-3进门,占到座位!开始吃饭 没有座位了pool-1-thread-5请等候 没有座位了pool-1-thread-4请等候 没有座位了pool-1-thread-6请等候 没有座位了pool-1-thread-8请等候 没有座位了pool-1-thread-10请等候 没有座位了pool-1-thread-7请等候 没有座位了pool-1-thread-9请等候 pool-1-thread-1吃完了,走了,又有空座位 了 pool-1-thread-5进门,占到座位!开始吃饭 pool-1-thread-3吃完了,走了,又有空座位 了 pool-1-thread-4进门,占到座位!开始吃饭 pool-1-thread-4吃完了,走了,又有空座位 了 pool-1-thread-6进门,占到座位!开始吃饭 pool-1-thread-2吃完了,走了,又有空座位 了 pool-1-thread-8进门,占到座位!开始吃饭 pool-1-thread-5吃完了,走了,又有空座位 了 pool-1-thread-10进门,占到座位!开始吃饭 pool-1-thread-10吃完了,走了,又有空座位 了 pool-1-thread-7进门,占到座位!开始吃饭 pool-1-thread-7吃完了,走了,又有空座位 了 pool-1-thread-9进门,占到座位!开始吃饭 pool-1-thread-6吃完了,走了,又有空座位 了 pool-1-thread-8吃完了,走了,又有空座位 了 pool-1-thread-9吃完了,走了,又有空座位 了