1. 程式人生 > >Java執行緒--Semaphore訊號量

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