介紹
線程是一個有趣的東西。在過去的評論中,我們研究了一些用於實現多線程的可用工具。讓我們看看我們還能做些什麼有趣的事情。在這一點上,我們知道了很多。例如,從“
Better together: Java and the Thread class. Part I — Threads of execution ”中,我們知道 Thread 類代表一個執行線程。我們知道線程執行一些任務。如果我們希望我們的任務能夠
run
,那麼我們必須用 標記線程
Runnable
。
![更好的結合:Java 和 Thread 類。 第六部分——開火! - 1]()
要記住,我們可以使用
Tutorialspoint 在線 Java 編譯器:
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();
}
我們也知道我們有一種叫做鎖的東西。
我們在“ Better together: Java and the Thread class. Part II — Synchronization”中了解了這一點。如果一個線程獲取鎖,那麼另一個試圖獲取鎖的線程將被迫等待鎖被釋放:
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();
}
}
我認為是時候談談我們可以做哪些其他有趣的事情了。
信號量
控制多少線程可以同時運行的最簡單方法是信號量。這就像一個鐵路信號。綠色表示繼續。紅色表示等待。等待信號量的什麼?使用權。要獲得訪問權限,我們必須獲取它。當不再需要訪問時,我們必須放棄或釋放它。讓我們看看這是如何工作的。我們需要導入
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
。不出所料,這是一個帶倒計時的閂鎖。這裡我們需要為該類提供適當的導入語句
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();
}
}
首先,我們先告訴latch to
countDown()
。谷歌將倒計時定義為“將數字倒序計數到零的行為”。然後我們告訴鎖存器到
await()
,即等到計數器變為零。有趣的是,這是一個一次性計數器。Java 文檔說,“當線程必須以這種方式重複倒計時時,請改用 CyclicBarrier”。換句話說,如果您需要一個可重複使用的計數器,您需要一個不同的選項:
CyclicBarrier
。
循環屏障
顧名思義,
CyclicBarrier
就是一個“可重複使用”的屏障。我們需要導入
java.util.concurrent.CyclicBarrier
類。讓我們看一個例子:
public static void main(String[] args) throws InterruptedException {
Runnable action = () -> System.out.println("On your mark!");
CyclicBarrier barrier = new CyclicBarrier(3, action);
Runnable task = () -> {
try {
barrier.await();
System.out.println("Finished");
} catch (BrokenBarrierException | InterruptedException e) {
e.printStackTrace();
}
};
System.out.println("Limit: " + barrier.getParties());
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
如您所見,線程運行該
await
方法,即等待。在這種情況下,障礙值會降低。
barrier.isBroken()
當倒計時達到零時,屏障被視為已損壞 ( )。要重置屏障,您需要調用沒有的
reset()
方法。
CountDownLatch
交換器
下一個機制是 Exchanger。在此上下文中,Exchange 是一個同步點,在該同步點中可以交換或交換事物的變化。如您所料,an
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() + " exchanged with " + withThreadName);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
new Thread(task).start();
}
這裡我們啟動兩個線程。它們中的每一個都運行 exchange 方法並等待另一個線程也運行 exchange 方法。這樣做時,線程交換傳遞的參數。有趣的。是不是讓你想起了什麼?它讓人想起
SynchronousQueue
,它位於 的核心
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");
}
這個例子表明,當一個新線程啟動時,它會等待,因為隊列是空的。然後主線程將“Message”字符串放入隊列中。更重要的是,它也會停止,直到從隊列中收到這個字符串。您還可以閱讀“
SynchronousQueue vs Exchanger ”以查找有關此主題的更多信息。
移相器
我們把最好的留到最後 —
Phaser
。我們需要導入
java.util.concurrent.Phaser
類。讓我們看一個簡單的例子:
public static void main(String[] args) throws InterruptedException {
Phaser phaser = new Phaser();
// By calling the register method, we register the current (main) thread as a party
phaser.register();
System.out.println("Phasecount is " + phaser.getPhase());
testPhaser(phaser);
testPhaser(phaser);
testPhaser(phaser);
// After 3 seconds, we arrive at the barrier and deregister. Number of arrivals = number of registrations = start
Thread.sleep(3000);
phaser.arriveAndDeregister();
System.out.println("Phasecount is " + phaser.getPhase());
}
private static void testPhaser(final Phaser phaser) {
// We indicate that there will be a +1 party on the Phaser
phaser.register();
// Start a new thread
new Thread(() -> {
String name = Thread.currentThread().getName();
System.out.println(name + " arrived");
phaser.arriveAndAwaitAdvance(); // The threads register arrival at the phaser.
System.out.println(name + " after passing barrier");
}).start();
}
該示例說明,當使用 時
Phaser
,當註冊數量與到達障礙的數量相匹配時,障礙就會中斷。
Phaser
您可以通過閱讀
這篇 GeeksforGeeks 文章來更加熟悉。
概括
從這些示例中可以看出,有多種方法可以同步線程。早些時候,我試圖回憶多線程的各個方面。我希望本系列的前幾期文章對您有所幫助。有人說,多線程之路是從《Java Concurrency in Practice》這本書開始的。雖然它是 2006 年出版的,但人們說這本書非常基礎並且今天仍然適用。例如,您可以閱讀此處的討論:
“Java Concurrency In Practice”是否仍然有效?. 閱讀討論中的鏈接也很有用。例如,有一個指向
The Well-Grounded Java Developer一書的鏈接,我們將特別提到
第 4 章現代並發性。還有關於此主題的完整評論:
《Java 並發實戰》在 Java 8 時代還有效嗎?該文章還提供了有關閱讀其他內容以真正理解該主題的建議。在那之後,你可以看看像
OCA/OCP Java SE 8 Programmer Practice Tests這樣的好書。我們對第二個首字母縮略詞感興趣:OCP(Oracle 認證專家)。您將在“第 20 章:Java 並發”中找到測試。這本書既有問題也有答案,並有解釋。例如:
![更好的結合:Java 和 Thread 類。 第六部分——開火! - 3]()
很多人可能會開始說這道題又是記憶方法的例子。一方面,是的。另一方面,您可以通過回憶那
ExecutorService
是 . 的一種“升級”來回答這個問題
Executor
。和
Executor
旨在簡單地隱藏線程的創建方式,但它並不是執行它們的主要方式,即
Runnable
在新線程上啟動一個對象。這就是為什麼沒有
execute(Callable)
— 因為在 中
ExecutorService
,
Executor
只需添加
submit()
可以返回
Future
對象的方法。當然,我們可以記住一個方法列表,但是根據我們對類本身性質的了解來做出答案要容易得多。以下是有關該主題的一些其他材料:
更好的結合:Java 和 Thread 類。第 I 部分 — 執行的線程 更好地結合:Java 和 Thread 類。第二部分 — 同步 更好地結合:Java 和 Thread 類。第 III 部分 — 更好地交互:Java 和 Thread 類。第 IV 部分 — Callable、Future 和朋友 更好地結合在一起:Java 和 Thread 類。第五部分 — 執行器、ThreadPool、Fork/Join
GO TO FULL VERSION