Java執行緒--Semaphore訊號量
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