您的位置:首页 > 编程语言 > Java开发

Java 信号量 Semaphore 介绍

2015-01-05 16:43 411 查看
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: 0Accessing: 1Accessing: 3Accessing: 4Accessing: 2-----------------0Accessing: 6-----------------1Accessing: 7-----------------1Accessing: 8-----------------1Accessing: 10-----------------1Accessing: 9-----------------1Accessing: 5-----------------1Accessing: 12-----------------1Accessing: 11-----------------1Accessing: 13-----------------1Accessing: 14-----------------1Accessing: 15-----------------1Accessing: 16-----------------1Accessing: 17-----------------1Accessing: 18-----------------1Accessing: 19一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采取相应的行动。拿到信号量的线程可以进入代码,否则就等待。通过acquire()和release()获取和释放访问许可。相关方法:

acquire

public void acquire()
throws InterruptedException
从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。获取一个许可(如果提供了一个)并立即返回,将可用的许可数减 1。如果没有可用的许可,则在发生以下两种情况之一前,禁止将当前线程用于线程安排目的并使其处于休眠状态:某些其他线程调用此信号量的
release()
方法,并且当前线程是下一个要被分配许可的线程;或者其他某些线程中断当前线程。如果当前线程:被此方法将其已中断状态设置为 on ;或者在等待许可时被
中断
。则抛出
InterruptedException
,并且清除当前线程的已中断状态。抛出:
InterruptedException
- 如果当前线程被中断

release

public void release()
释放一个许可,将其返回给信号量。释放一个许可,将可用的许可数增加 1。如果任意线程试图获取许可,则选中一个线程并将刚刚释放的许可给予它。然后针对线程安排目的启用(或再启用)该线程。不要求释放许可的线程必须通过调用
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 blocke.printStackTrace();}show(Thread.currentThread().getName()+"进门,占到座位!开始吃饭");try {Thread.sleep(new Random().nextInt(5000));} catch (InterruptedException e) {// TODO Auto-generated catch blocke.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吃完了,走了,又有空座位 了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: