1.訊號量的概念:

訊號量是一種計數器,用來保護一個或者多個共享資源的訪問,它是併發程式設計的一種基礎工具,大多數程式語言都提供了這個機制。

2、訊號量控制執行緒訪問流程:

如果執行緒要訪問一個共享資源,它必須先獲得訊號量。如果訊號量的內部計數器大於0,訊號量將減1,然後允許訪問這個共享資源,計數器大於0意味著有可能使用的資源,因此執行緒將被允許使用其中一個資源,否則,如果訊號量計數器等於0,訊號量將會把執行緒置入休眠直到計數器大於0,計數器等於0的時候意味著所有的共享資源已經被其他執行緒使用了,所以需要訪問這個共享源的執行緒必須等待。

當執行緒使用完某個共享資源時,訊號量必須被釋放,以便於其他執行緒能夠訪問共享資源,釋放操作將使訊號量的內部計數器加1。

練習程式碼:

/**
 * 實現一個列印佇列,總共有三個印表機,當三個印表機都在工作的時候,其他列印的執行緒任務將阻塞,
 * 其中一個印表機被釋放後,阻塞的執行緒才能獲取印表機進行列印。
 * @author Administrator
 *
 */
class PrintQueue {
	//申明一個訊號量
	private final Semaphore semaphore;
	//boolean陣列,用來記錄印表機狀態
	private boolean freeprinters[];
	//Lock鎖,當獲取印表機狀態時,進行鎖定,防止多個執行緒使用同一臺印表機
	private Lock lockPrinters;
	
	public PrintQueue() {
		semaphore = new Semaphore(3);
		freeprinters = new boolean[3];
		for(int i = 0 ; i < 3;i++){
			freeprinters[i] = true;
		}
		lockPrinters = new ReentrantLock();
	}
	
	public void printJob(Object document){
		try{
			semaphore.acquire();//獲取訊號量,如果訊號量為0,則執行緒阻塞,如果答應0,則訊號量-1
			int assignedPrinter = getPrinter();
			long duration = (long)(Math.random()*10);
			System.out.printf("%s:PrintQueue:Printing a Job in Printer %d during %d seconds\r\n"
						,Thread.currentThread().getName(),assignedPrinter,duration);
			TimeUnit.SECONDS.sleep(duration);
			freeprinters[assignedPrinter] = true;
		}catch(InterruptedException e){
			e.printStackTrace();
		}finally {
			semaphore.release();//訊號量+1
		}
	}
	
	private int getPrinter(){
		int ret = -1;
		try{
			lockPrinters.lock();
			for(int i = 0; i < freeprinters.length;i++){
				if(freeprinters[i]){
					ret = i;
					freeprinters[i] = false;
					break;
				}
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			lockPrinters.unlock();
		}
		return ret;
	}
}

class Job implements Runnable{
	private PrintQueue printQueue;
	
	public Job(PrintQueue printQueue) {
		this.printQueue = printQueue;
	}
	
	@Override
	public void run() {
		System.out.printf("%s :Going to print a job\r\n",Thread.currentThread().getName());
		printQueue.printJob(new Object());
		System.out.printf("%s:The document has been printed\r\n",Thread.currentThread().getName());
	}
	
}

public class PrintQueueDemo{
	public static void main(String args[]){
		PrintQueue printQueue = new PrintQueue();
		Thread[] threads = new Thread[10];
		for(int i = 0 ; i < 10 ; i++){
			threads[i] = new Thread(new Job(printQueue), "Thread"+i);
		}
		for(int i = 0 ; i < 10 ; i++){
			threads[i].start();
		}
	}
}