信號量(semaphore)機制是一種常用的同步機制,在現代OS中被廣泛采用。semaphore是一個非0值,當它的值大於0時表示系統目前還有足夠的資源分配給請求線程,每分配出去一個資源,值遞減。當值等於0時表示當前已無資源可分配。JDK提供了Semaphore類來實現信號量。
假如我們一共有3台打印機可用,當前有N個線程都請求使用打印機,要實現對打印機這種資源的訪問保護,有以下兩種方式:
顯然,方式一在某個時間點只能有一個線程在使用打印機,那麼另外2台打印機資源就被浪費掉了。有人可能會想,如果只對檢查並分配打印機的代碼進行加鎖,而在使用打印機的過程中不加鎖,這樣當第一個線程使用打印機時,第二個線程依然能夠請求到打印機,不就能允許3個線程都用上打印機了嗎?
這樣做是非常危險的,我們稱之為資源逃逸。如果不對使用打印機的過程加鎖,那麼如果請求線程又將這台逃逸出的打印機分配給了其它線程,那麼線程同步就失去了意義。
對於方式二,當線程A使用打印機時,線程B依然可以請求並使用打印機,線程C也可以。當線程A使用完後必須釋放semaphore, 這樣其它線程又可以使用這台打印機了。
首先定義打印機對象:
class Printer {
private String name;
private int index;
public Printer(String name, int index) {
this.name = name;
this.index = index;
}
public void print() {
System.out.println("printer " + name + " is working for " + Thread.currentThread().getName());
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("printer " + name + " for " + Thread.currentThread().getName() + " is done");
}
public int getIndex() {
return this.index;
}
}
然後,我們定義一個打印機池(Pool)。利用這個pool實現打印機資源的安全分配:
class PrinterPool {
/**
* store all available devices
*/
private Printer[] printers;
private boolean[] freePrinters;
private Lock lock = new ReentrantLock();
/**
* create a semaphore for 3 resources
*/
private Semaphore semaphore = new Semaphore(3);
/**
* 3 printers available in this pool.
*/
public PrinterPool() {
printers = new Printer[] {
new Printer("A", 0),
new Printer("B", 1),
new Printer("C", 2)
};
freePrinters = new boolean[] {
true,
true,
true
};
}
/**
* use printer
*/
public Printer printData() {
Printer printer = null;
try {
semaphore.acquire();
while (true) {
printer = getFreePrinter();
if (printer != null) {
break;
}
}
// use printer
printer.print();
// reclaim printer
reclaimPrinter(printer);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
return printer;
}
private Printer getFreePrinter() {
Printer printer = null;
lock.lock();
for (int ix = 0 ; ix < freePrinters.length ; ++ix) {
if (true == freePrinters[ix]) {
freePrinters[ix] = false;
printer = printers[ix];
break;
}
}
lock.unlock();
return printer;
}
private void reclaimPrinter(Printer p) {
lock.lock();
this.freePrinters[p.getIndex()] = true;
lock.unlock();
}
}
最後編寫測試代碼,啟動10個線程請求使用打印機:
// create printer pool
PrinterPool pool = new PrinterPool();
Runnable job = () -> {
pool.printData();
};
// create 10 threads
Thread[] threads = new Thread[10];
for (int ix = 0 ; ix < 10 ; ++ix) {
threads[ix] = new Thread(job);
}
// start all threads
for (int ix = 0 ; ix < 10 ; ++ix) {
threads[ix].start();
}
輸出結果: