歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Java: 使用信號量(Semaphore)保護多個共享資源的訪問

Java: 使用信號量(Semaphore)保護多個共享資源的訪問

日期:2017/3/1 9:30:06   编辑:Linux編程

信號量(semaphore)機制是一種常用的同步機制,在現代OS中被廣泛采用。semaphore是一個非0值,當它的值大於0時表示系統目前還有足夠的資源分配給請求線程,每分配出去一個資源,值遞減。當值等於0時表示當前已無資源可分配。JDK提供了Semaphore類來實現信號量。

假如我們一共有3台打印機可用,當前有N個線程都請求使用打印機,要實現對打印機這種資源的訪問保護,有以下兩種方式:

  1. 每當一個線程請求使用打印機時,檢查當前是否有空閒打印機可用,如果有則分配給請求線程並使用打印機。分配和使用打印機的過程都要加鎖(同一把鎖)。
  2. 創建一個初值為3的semaphore, 允許3個線程同時使用打印機。

顯然,方式一在某個時間點只能有一個線程在使用打印機,那麼另外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();
}

輸出結果:

Copyright © Linux教程網 All Rights Reserved