Semaphore信号量
目录
Semaphore是计数信号量。Semaphore可管理1个或多个许可证。
- acquire()方法尝试获取一个许可证,如果没有获得到,当前线程阻塞,直至其它线程的release()方法释放出来一个许可证;
- release()方法释放出来一个许可证;
Semaphore含义:有多少许可证,就能同时允许多少线程并行。这要看当前机器CPU的最大线程支持数N(理论上来说,程序上给与设定的最佳并发线程数为N+1)。信号量主要用于两个目的:一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。下面举例说明业务场景。
示例1:生产者消费者----产1消1模式
产1消1:业务说明
- 业务初始情况:
原料若干,生产者1人,消费者1人,加工房1间(共享资源)。
- 业务达到效果:
生产者每加工出来一个原料,则消费者立马取走消费。
- 业务注意事项:
生产者和消费者不用共用一个信号指示灯,原因是由于并发性,有可能生产者总是能获得到许可证,疯狂的生产导致生产过剩,消费饥饿现象;也有可能消费者总是能获得到许可证,疯狂的消费导致消费过剩(其实啥都没消费到,因为还没生产呢),生产饥饿现象;所以,生产者/消费者的并发问题,我们在此启用一个生产信号指示灯,一个消费信号指示灯。生产者获得可生产的指示灯后,进行生产直至生产完毕,然后点亮消费信号指示灯使消费者具备资格进行消费。消费者获得可消费的指示灯后,进行消费直至消费完毕,然后点亮生产信号指示灯使生产者具备资格进行生产。
系统初始时,加工房共享资源是空闲着的,有硬性要求必须要先进行生产,然后才能消费。
- 业务实现图示:
- 业务示例代码:
/**
* Thread线程:代码结构的一般写法
* 业务类:包含共享资源以及共享资源的访问方法
* 任务:实现Runnable,一个任务代表一个业务类需要并发执行的业务方法
//业务类
class MyClass{
private int n; //共享资源
public void set(int n){
this.n = n;
}
public int get(){
return n;
}
}
//任务
class MyTask implements Runnable{
private Semaphore sem ;//同步器|锁
private MyClass myObj; //业务类对象
public MyTask(MyClass myObj, Semaphore sem){
this.sem = sem;
this.myObj = myObj;
}
public void run(){
sem.acquire(); //获得锁
myObj.yyyMethod(); //临界区:业务代码
sem.release(); //释放锁
}
}
*/
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
import java.util.concurrent.atomic.*;
/**
业务类
*/
class MyClass{
private int n;
public void set(int n){
this.n = n;
System.out.println("生产set:"+n+"累了睡1秒");
}
public int get(){
System.out.println("消费get:"+n);
return n ;
}
}
/**
任务:生产
*/
class ProduceTask implements Runnable{
private Semaphore semP ;//生产指示灯,同步器|锁
private Semaphore semC ;//消费指示灯,同步器|锁
private MyClass myObj; //业务类对象
public ProduceTask(MyClass myObj, Semaphore semP, Semaphore semC){
this.semP = semP;
this.semC = semC;
this.myObj = myObj;
}
public void run(){
for(int i=0;i<5;i++){
try{
semP.acquire(); //获得生产信号
myObj.set(i); //临界区:业务代码,进行生产
Thread.sleep(1000); //睡眠1秒,释放cpu,但没给出消费信号,所以消费无法进行
semC.release(); //释放消费信号
}
catch(InterruptedException e){
}
}
}
}
/**
任务:消费
*/
class ConsumeTask implements Runnable{
private Semaphore semP ; //生产指示灯,同步器|锁
private Semaphore semC ; //消费指示灯,同步器|锁
private MyClass myObj ; //业务类对象
public ConsumeTask(MyClass myObj, Semaphore semP, Semaphore semC){
this.semP = semP;
this.semC = semC;
this.myObj = myObj;
}
public void run(){
for(int i=0;i<5;i++){
try{
semC.acquire(); //获得消费信号
myObj.get(); //临界区:业务代码,进行消费
semP.release(); //释放生产信号
}
catch(InterruptedException e){
}
}
}
}
/**
**测试**
*/
public class TestOnePsemOneCsem{
public static void main(String[] args){
Semaphore semP = new Semaphore(1); //生产指示灯初始时有1个许可证,同步器|锁
Semaphore semC = new Semaphore(0); //消费指示灯初始时有0个许可证,同步器|锁
MyClass myObj = new MyClass(); //业务类对象
ConsumeTask cTask = new ConsumeTask(myObj, semP, semC); //生产任务
ProduceTask pTask = new ProduceTask(myObj, semP, semC); //消费任务
Thread cThread = new Thread(cTask,"consumeThread");
Thread pThread = new Thread(pTask,"produceThread");
cThread.start();
pThread.start();
}
}
运行产1消1示例代码,打印如下:
生产set:0累了睡1秒
消费get:0
生产set:1累了睡1秒
消费get:1
生产set:2累了睡1秒
消费get:2
生产set:3累了睡1秒
消费get:3
生产set:4累了睡1秒
消费get:4
示例2:多信号指示灯----停车场
停车场:业务说明
- 业务初始情况:
信号指示灯若干(表征当前车场内还有多少空车位),车场内有空车位若干(共享资源)
车辆若干想进入车场
车辆离开车场
- 业务达到效果:
有多少个停车信号指示灯,就允许同时多少量车进入车场。车辆进入车场后,依据导向线,别多辆车都停到了同一个空的车位上。
- 业务注意事项:
车场外有很多车辆想停入车场(也即:多线程),车场内的空车位是有限的。需要达到的效果为:有多少空车位(N个),就有多少个可入车场的信号指示灯亮(N>=1),同时允许N辆车入场,但需要强制控制每辆车只能听到空车位上,不能多辆车都往一个空车位停,所以需要每个车位都是互斥的共享资源。总结下来就是:信号指示灯指示能同时停多少辆车,车具体停时,要保证空车位共享资源的互斥访问。
- 业务实现图示:
- 业务示例代码:
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
import java.util.concurrent.atomic.*;
/**
* 车位
*/
class Park
{
private int num; //车位号
private boolean flag; //车位可用标志
public Park(int num, boolean flag){
this.num = num;
this.flag = flag;
}
public Park(){}
public void setNum(int num){
this.num = num;
}
public int getNum(){
return this.num;
}
public void setFlag(boolean flag){
this.flag = flag;
}
public boolean getFlag(){
return this.flag;
}
@Override
public String toString() {
String s = flag?"空":"被占用";
return "Park{num=" + num + " : "+s+"}";
}
}
/**
* 停车场:每一个车位都是共享资源,若干个车位组合在一起构成停车场.获取车位时,需要加持锁来互斥获取.
*/
class ParkGround
{
public ParkGround(int i){
parks = new Park[i];
for(int j=0;j<i;j++){
parks[j] = new Park(j+1,true);
}
}
//资源:若干车位
private Park[] parks;
//锁:每个车位都是互斥共享资源,防止多辆车停到同一个车位上(注意:资源是车位,不是停车场)
private Lock locker = new ReentrantLock();
//获取一个空车位
public Park obtainPark(){
Park park = null;
//获得锁
locker.lock();
//临界区:共享资源
for (int i = 0 ; i < parks.length ; i++) {
if ( parks[i].getFlag() ) {
parks[i].setFlag(false);
park = parks[i];
break;
}
}
//释放锁
locker.unlock();
return park;
}
//释放一个车位
public void releasePark(Park park){
if( null!=park && !park.getFlag() ){
park.setFlag(true);
String tName = Thread.currentThread().getName();
System.out.println( tName+"释放Park:num=" + park.getNum() );
}
}
}
/**
*【车场工作场景】
*
* 任务:从车场获得空车位,随机停一段时间后,然后离开车场
*/
class GroundWorkTask implements Runnable
{
private ParkGround g;
private Semaphore sem;
public GroundWorkTask(ParkGround g,Semaphore sem){
this.g = g ;
this.sem = sem ;
}
public void run(){
Park park = null;
try {
/**
*获得信号:尝试获取停车场有空车位的信号
*/
sem.acquire();
/**
* 临界区:1获得空车位,2随机停段时间,3离开
*/
//临界区:1获得空车位(停车场的信号仅表示可入车场,空车位具体在哪,循环处理)
while (true) {
park = g.obtainPark();
if (park != null) break;
}
//临界区:2随机停段时间
long sleepTime = (long)(Math.random() * 2500) ;
String tName = Thread.currentThread().getName();
System.out.println(tName+"===="+park+"我停时["+sleepTime+"]就离开!\n\r");
Thread.sleep(sleepTime);
//临界区:3离开
g.releasePark(park);
}
catch (InterruptedException e) {
e.printStackTrace();
}
finally {
/**
*释放信号:车辆开走一辆,释放停车场又有空车位了
*/
sem.release();
}
}
}
public class SemaphoreDemo {
public static void main(String[] args) {
int parks = 5 ;//5个车位
Semaphore sem = new Semaphore(parks); //信号量:同时允许五辆车进入停车场
ParkGround g = new ParkGround(parks); //停车场:
GroundWorkTask eTask = new GroundWorkTask(g,sem); //任务:进场,停会,离开
Thread[] threads = new Thread[10]; //十辆车想做任务:进,停,离
for (int i = 0 ; i < 10 ; ++i) {
threads[i] = new Thread(eTask,"T"+i);
threads[i].start();
}
}
}
运行停车场测试代码,打印如下:
T1====Park{num=2 : 被占用}我停时[351]就离开!
T6====Park{num=5 : 被占用}我停时[1439]就离开!
T0====Park{num=1 : 被占用}我停时[341]就离开!
T3====Park{num=3 : 被占用}我停时[2414]就离开!
T5====Park{num=4 : 被占用}我停时[188]就离开!
T5释放Park:num=4
T4====Park{num=4 : 被占用}我停时[887]就离开!T0释放Park:num=1
T8====Park{num=1 : 被占用}我停时[638]就离开!T1释放Park:num=2
T7====Park{num=2 : 被占用}我停时[693]就离开!T8释放Park:num=1
T9====Park{num=1 : 被占用}我停时[26]就离开!T9释放Park:num=1
T2====Park{num=1 : 被占用}我停时[2192]就离开!T7释放Park:num=2
T4释放Park:num=4
T6释放Park:num=5
T3释放Park:num=3
T2释放Park:num=1