介紹
流是一件有趣的事。在先前的評論中,我們研究了一些用於實現多線程的可用工具。讓我們看看我們還能做哪些有趣的事。此時我們知道了很多。例如,從「
你不能用執行緒破壞 Java:第一部分 - 執行緒」中,我們知道執行緒就是執行緒。我們知道一個線程正在執行某些任務。如果我們希望我們的任務能夠運行(
run
),那麼我們必須將執行緒指定為某個
Runnable
。
要記住,我們可以使用
Tutorialspoint Java Online Compiler:
public static void main(String []args){
Runnable task = () -> {
Thread thread = Thread.currentThread();
System.out.println("Hello from " + thread.getName());
};
Thread thread = new Thread(task);
thread.start();
}
我們也知道,我們有一個鎖這樣的概念。
我們在《你不能用線程破壞 Java:第二部分 - 同步》中讀到了相關內容。一個執行緒可以佔用一個鎖,然後另一個嘗試佔用該鎖的執行緒將被迫等待該鎖釋放:
import java.util.concurrent.locks.*;
public class HelloWorld{
public static void main(String []args){
Lock lock = new ReentrantLock();
Runnable task = () -> {
lock.lock();
Thread thread = Thread.currentThread();
System.out.println("Hello from " + thread.getName());
lock.unlock();
};
Thread thread = new Thread(task);
thread.start();
}
}
我認為是時候談談我們還可以做哪些有趣的事情了。
信號量
控制可以同時工作的執行緒數量最簡單的方法是信號量。就像在鐵路上一樣。綠燈亮了——你可以。紅燈亮了——我們正在等待。我們對信號量有何期望?權限。英語許可 - 許可。要獲得許可,您需要獲得它,這在英語中是“acquire”。而當不再需要該許可時,我們必須將其放棄,即釋放它或擺脫它,英語中將是釋放。讓我們看看它是如何工作的。我們需要導入該類別
java.util.concurrent.Semaphore
。例子:
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(0);
Runnable task = () -> {
try {
semaphore.acquire();
System.out.println("Finished");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
Thread.sleep(5000);
semaphore.release(1);
}
正如我們所看到的,記住英語單字後,我們就了解了信號量的工作原理。有趣的是,主要條件是信號量「帳戶」必須具有正數的許可證。因此,您可以用減號來啟動它。並且您可以請求(獲取)超過 1 個。
倒數鎖存器
下一個機制是
CountDownLatch
。英文中的CountDown是倒數計時,Latch是螺栓或閂鎖。也就是說,如果我們翻譯一下,那麼這是一個帶有倒數計時的鎖存器。這裡我們需要適當的導入該類別
java.util.concurrent.CountDownLatch
。這就像一場比賽或一場比賽,每個人都聚集在起跑線上,當每個人都準備好時,獲得許可,每個人同時開始。例子:
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
Runnable task = () -> {
try {
countDownLatch.countDown();
System.out.println("Countdown: " + countDownLatch.getCount());
countDownLatch.await();
System.out.println("Finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
英文中的await-期待。也就是我們先說話
countDown
。正如Google翻譯所說,倒數計時是“將數字逆序計數到零的行為”,即執行倒數動作,其目的是數到零。然後我們說
await
- 即等待計數器值變為零。有趣的是,這樣的櫃檯是一次性的。正如JavaDoc中所說—“當執行緒必須以這種方式重複計數時,請改為使用CyclicBarrier”,也就是說,如果您需要可重用的計數,則需要使用另一個選項,稱為
CyclicBarrier
。
循環勢壘
顧名思義,
CyclicBarrier
它是一個週期性障礙。我們需要導入該類別
java.util.concurrent.CyclicBarrier
。讓我們來看一個例子:
public static void main(String[] args) throws InterruptedException {
Runnable action = () -> System.out.println("На старт!");
CyclicBarrier berrier = new CyclicBarrier(3, action);
Runnable task = () -> {
try {
berrier.await();
System.out.println("Finished");
} catch (BrokenBarrierException | InterruptedException e) {
e.printStackTrace();
}
};
System.out.println("Limit: " + berrier.getParties());
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
可以看到,線程正在執行
await
,即等待。在這種情況下,障礙的價值會降低。
berrier.isBroken()
當倒數計時達到零時,障礙被視為已損壞 ( )。要重置障礙,您需要調用
berrier.reset()
,而 中缺少該函數
CountDownLatch
。
交換器
下一個補救措施是
Exchanger
。Exchange源自英語,譯為交換或交換。A
Exchanger
是交換者,即他們透過某種東西進行交換。讓我們來看一個簡單的例子:
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
Runnable task = () -> {
try {
Thread thread = Thread.currentThread();
String withThreadName = exchanger.exchange(thread.getName());
System.out.println(thread.getName() + " обменялся с " + withThreadName);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
new Thread(task).start();
}
這裡我們啟動兩個線程。它們中的每一個都執行交換方法並等待另一個執行緒也執行交換方法。因此,執行緒將在它們之間交換傳遞的參數。有趣的事情。她沒有提醒你什麼嗎?他提醒說
SynchronousQueue
,這是「a」的核心
cachedThreadPool
。為了清楚起見,以下是一個例子:
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<String> queue = new SynchronousQueue<>();
Runnable task = () -> {
try {
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
queue.put("Message");
}
該範例顯示,透過啟動一個新線程,該線程將進入等待模式,因為 隊列將為空。然後
main
線程會將文字“Message”排隊。同時,它將停止所需的時間,直到從佇列中接收到該文字元素。關於這個主題,您也可以閱讀“
SynchronousQueue Vs Exchanger ”。
移相器
最後,最甜蜜的事——
Phaser
。我們需要導入該類別
java.util.concurrent.Phaser
。讓我們來看一個簡單的例子:
public static void main(String[] args) throws InterruptedException {
Phaser phaser = new Phaser();
phaser.register();
System.out.println("Phasecount is " + phaser.getPhase());
testPhaser(phaser);
testPhaser(phaser);
testPhaser(phaser);
Thread.sleep(3000);
phaser.arriveAndDeregister();
System.out.println("Phasecount is " + phaser.getPhase());
}
private static void testPhaser(final Phaser phaser) {
phaser.register();
new Thread(() -> {
String name = Thread.currentThread().getName();
System.out.println(name + " arrived");
phaser.arriveAndAwaitAdvance();
System.out.println(name + " after passing barrier");
}).start();
}
此範例表明,使用
Phaser
「a」時,當註冊數量與到達屏障的數量一致時,屏障就會被破壞。
您可以在「新 Phaser 同步器Phaser
」中心的文章中找到更多資訊。
結果
正如您從範例中看到的,同步線程有多種方法。早些時候我試著記住一些有關多線程的知識,我希望前面的部分有用。他們說多線程之路始於《Java並發實踐》一書。雖然這本書是 2006 年出版的,但人們反映這本書很基礎,而且仍然很有衝擊力。例如,您可以閱讀此處的討論:「
Java 並發實踐中仍然有效嗎?」。閱讀討論中的連結也很有幫助。例如,有一個“
The Well-Grounded Java Developer ”一書的鏈接,其中值得關注的“
Chapter 4. Modern concurrency ”。關於同一主題還有另一篇完整的評論:「
Java 並發實踐在 Java 8 時代是否仍然相關」。它還提供了有關您還應該閱讀哪些內容才能真正理解主題的提示。之後,您可以仔細看看《
OCA OCP JavaSE 8程式設計師實作測驗》這樣一本精彩的書。我們感興趣的是第二部分,即OCP。並且“∫”中有測試。本書包含問題和答案以及解釋。例如:
許多人可能會開始說這只是另一種方法的記憶。一方面,是的。另一方面,這個問題可以透過記住
ExecutorService
這是一種「升級」來回答
Executor
。它
Executor
的目的只是隱藏創建線程的方法,而不是隱藏執行線程的主要方式,即在新線程中運行
Runnable
。因此,
execute(Callable)
不,因為 他們只是添加了
ExecutorService
可以返回的方法。正如您所看到的,我們可以記住方法列表,但如果我們知道類別本身的性質,猜測會容易得多。好吧,關於這個主題的一些附加資料:
Executor
submit
Future
#維亞切斯拉夫
GO TO FULL VERSION