semaphore是一個計數(shù)器,它保護對一個或多個共享資源的訪問。在本教程中,我們將學(xué)習(xí)如何使用二進制semaphore來控制多線程對共享資源的訪問。
Semaphores是如何工作的?
您可以將semaphore想象成計數(shù)器,計數(shù)器可以遞增或遞減。用一個數(shù)字(即5)初始化semaphore。現(xiàn)在這個semaphore可以連續(xù)減少最多5次,直到計數(shù)器達到0。一旦計數(shù)器為0,您可以將其增加5次,使其最大為5。semaphore的計數(shù)器值必須始終在限制0 >= n >= 5的范圍內(nèi)(在本例中)。
顯然,semaphore不僅僅是計數(shù)器。當計數(shù)器值為0時,它們能夠使線程等待,也就是說,它可以充當具有計數(shù)器功能的鎖。
從多線程的角度來說,當一個線程想要訪問一個共享資源(由semaphore保護)時,首先,它必須獲得這個semaphore。如果semaphore的內(nèi)部計數(shù)器大于0,則semaphore遞減計數(shù)器并允許訪問共享資源。否則,如果semaphore的計數(shù)器為0,semaphore將使線程休眠,直到計數(shù)器大于0。計數(shù)器中的值為0意味著所有共享資源都被其他線程使用,因此希望使用其中一個資源的線程必須等到其中一個資源空閑出來。
當線程完成共享資源的使用后,它必須釋放
semaphore,以便其他線程能夠訪問共享資源。該操作增加了semaphore的內(nèi)部計數(shù)器。
什么時候使用二進制Semaphore?
很明顯,二進制semaphore可以是0或1。這意味著二進制semaphore保護對單個共享資源的訪問,因此信號量的內(nèi)部計數(shù)器只能接受值1或0。
因此,當您需要保護對多線程訪問的單個資源的訪問時,可以使用二進制semaphore。
閱讀更多:如何在Java中使用鎖
如何使用二進制Semaphore?
為了展示二進制semaphore的用法,我們將實現(xiàn)一個打印隊列,并發(fā)任務(wù)可以使用該隊列打印它們的作業(yè)。這個打印隊列將受到二進制semaphore的保護,因此一次只能打印一個線程。
PrintingJob.java
這個類表示可以提交給打印機的獨立打印。這個類實現(xiàn)Runnable接口,這樣打印機就可以在輪到它時執(zhí)行它。
public class PrintingJob implements Runnable {
private PrinterQueue printerQueue;
public PrintingJob(PrinterQueue printerQueue) {
this.printerQueue = printerQueue;
}
public void run() {
System.out.printf("%s: Going to print a document\n", Thread.currentThread().getName());
printerQueue.printJob(new Object());
}
}
PrinterQueue.java
該類表示打印機隊列/打印機。請注意,我們將值1作為這個semaphore的構(gòu)造函數(shù)的參數(shù)傳遞,因此您正在創(chuàng)建一個二進制semaphore。
public class PrinterQueue {
private final Semaphore semaphore;
public PrinterQueue() {
semaphore = new Semaphore(1);
}
public void printJob(Object document) {
try {
semaphore.acquire();
Long duration = (long) (Math.random() * 10000);
System.out.println(Thread.currentThread().getName() + ": PrintQueue: Printing a Job during " + (duration / 1000) + " seconds :: Time - " + new Date());
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.printf("%s: The document has been printed\n", Thread.currentThread().getName());
semaphore.release();
}
}
}
讓我們測試一下打印程序:
public class SemaphoreExample {
public static void main(String[] args) {
PrinterQueue printerQueue = new PrinterQueue();
Thread thread[] = new Thread[10];
for (int i = 0; i < 10; i++) {
thread[i] = new Thread(new PrintingJob(printerQueue), "Thread " + i);
}
for (int i = 0; i < 10; i++) {
thread[i].start();
}
}
}
輸出結(jié)果為:
Thread 0: Going to print a document
Thread 9: Going to print a document
Thread 8: Going to print a document
Thread 7: Going to print a document
Thread 6: Going to print a document
Thread 5: Going to print a document
Thread 4: Going to print a document
Thread 3: Going to print a document
Thread 2: Going to print a document
Thread 1: Going to print a document
Thread 9: PrintQueue: Printing a Job during 2 seconds :: Time - Sun Mar 03 13:46:03 CST 2019
Thread 9: The document has been printed
Thread 8: PrintQueue: Printing a Job during 2 seconds :: Time - Sun Mar 03 13:46:06 CST 2019
Thread 8: The document has been printed
Thread 6: PrintQueue: Printing a Job during 4 seconds :: Time - Sun Mar 03 13:46:09 CST 2019
Thread 6: The document has been printed
Thread 7: PrintQueue: Printing a Job during 4 seconds :: Time - Sun Mar 03 13:46:13 CST 2019
Thread 7: The document has been printed
Thread 5: PrintQueue: Printing a Job during 7 seconds :: Time - Sun Mar 03 13:46:17 CST 2019
Thread 5: The document has been printed
Thread 4: PrintQueue: Printing a Job during 9 seconds :: Time - Sun Mar 03 13:46:25 CST 2019
Thread 4: The document has been printed
Thread 3: PrintQueue: Printing a Job during 4 seconds :: Time - Sun Mar 03 13:46:35 CST 2019
Thread 3: The document has been printed
Thread 2: PrintQueue: Printing a Job during 1 seconds :: Time - Sun Mar 03 13:46:39 CST 2019
Thread 2: The document has been printed
Thread 0: PrintQueue: Printing a Job during 5 seconds :: Time - Sun Mar 03 13:46:41 CST 2019
Thread 0: The document has been printed
Thread 1: PrintQueue: Printing a Job during 1 seconds :: Time - Sun Mar 03 13:46:46 CST 2019
Thread 1: The document has been printed
查看printJob()方法。此方法展示了在使用semaphore實現(xiàn)臨界區(qū)和保護對共享資源的訪問時必須遵循的三個步驟:
- 首先,使用
acquire()方法獲取semaphore。 - 然后,對共享資源執(zhí)行必要的操作。
- 最后,使用
release()方法釋放semaphore。
Semaphore類的構(gòu)造函數(shù)中允許第二個參數(shù)。此參數(shù)必須采用布爾值。如果您給它false值,您就創(chuàng)建了一個semaphore,它將在非公平模式下工作。這是默認選項。如果您給它true值,您就創(chuàng)建了一個semaphore,它將在公平模式下工作。