信号灯(Semaphore)是java5的新特性,仍然在java的并发库java.util.concurrent下。Java并发库的Semaphore 可以很轻松完成信号量控制,Semaphore分为单值和多值。单值只允许一个线程访问,多值允许多个线程同时访问。


举个例子,例如公司的打卡系统,如果有一个打卡机,那么一次就只能有一个人打卡,其余的人就被阻塞住,打卡完以后就可由下一个人打卡。如果有3个打卡机,那么一次就允许3个人或者少于三个人打卡,其余的人就得等待打卡机空闲下来才能继续打卡。


在信号量上我们定义两种操作: acquire(获取许可) 和 Release(释放)。信号量是一个非负整数,如上例中打卡机的数量。没当有人进行打卡,就会将该数量减一,当该数为0时。剩余的所有人(线程)都将处于等待。当有一个人打卡时,会调用acquire()方法,打卡机数量减一,打卡完成后,会调用Release()方法将打卡机空闲出来,排在后面的人就可以操作了。


当然了,信号量有公平和非公平模式。非公平模式允许后面的请求插队排在前面。


Semaphore(int permits, boolean fair)//permits为可通过的线程数,fair为是否为公平模式

其实要用信号灯来实现对线程的控制也非常简单,看个例子就会明白:


public class ThreadSemaphoreTest {

	public static void main(String[] args) {
		//启动线程池,有多少线程启动多少
		ExecutorService pools = Executors.newCachedThreadPool();
		//启动信号灯,设置一次可进入3个。是否公平默认的情况为true,所以直接new Semaphore(N)就行。
		final Semaphore sem = new Semaphore(3,true);
		//new10个线程
		for(int i = 1 ; i < 10 ; i++){
			//内部类使用外部类的变量必须是final型
			final int index = i;
			Thread thread = new Thread(new Runnable() {
				public void run() {
					try{
						//获取许可
						sem.acquire();
						System.out.println("已进入"+index+"个线程,还可进入"+sem.availablePermits()+"个");
						Thread.sleep(1000);
						//释放资源
						sem.release();
						System.out.println("空余出"+sem.availablePermits()+"个");
					}catch(Exception e){
						e.printStackTrace();
					}
				}
			});
			pools.execute(thread);
		}
		pools.shutdown();
	}
}

打印结果:

已进入1个线程,还可进入2个
已进入2个线程,还可进入1个
已进入3个线程,还可进入0个
空余出1个
已进入4个线程,还可进入0个
已进入6个线程,还可进入0个
空余出0个
空余出1个
已进入5个线程,还可进入0个
空余出0个
已进入8个线程,还可进入0个
空余出1个
已进入7个线程,还可进入0个
已进入9个线程,还可进入0个
空余出0个
空余出1个
空余出3个
空余出3个

从打印结果可以看出,虽然一次可进入三个线程,但并不是要等三个线程全部空出来才可以进入其他线程。而是只要有一个空闲,等待的线程立刻补上。这和现实生活中的情景式多么相似阿。由此可见,信号灯用来控制线程是很人性化的。