Java线程--Semaphore信号量

Semaphore信号量

目录

Semaphore信号量

示例1:生产者消费者----产1消1模式

产1消1:业务说明

示例2:多信号指示灯----停车场

停车场:业务说明



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

猜你喜欢

转载自blog.csdn.net/mmlz00/article/details/83689017