CodeGym /Java Blog /Toto sisi /更好的結合:Java 和 Thread 類。第六部分——開火!
John Squirrels
等級 41
San Francisco

更好的結合:Java 和 Thread 類。第六部分——開火!

在 Toto sisi 群組發布

介紹

線程是一個有趣的東西。在過去的評論中,我們研究了一些用於實現多線程的可用工具。讓我們看看我們還能做些什麼有趣的事情。在這一點上,我們知道了很多。例如,從“ 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 是一個同步點,在該同步點中可以交換或交換事物的變化。如您所料,anExchanger是執行交換或交換的類。讓我們看一個最簡單的例子:

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)— 因為在 中ExecutorServiceExecutor只需添加submit()可以返回Future對象的方法。當然,我們可以記住一個方法列表,但是根據我們對類本身性質的了解來做出答案要容易得多。以下是有關該主題的一些其他材料: 更好的結合:Java 和 Thread 類。第 I 部分 — 執行的線程 更好地結合:Java 和 Thread 類。第二部分 — 同步 更好地結合:Java 和 Thread 類。第 III 部分 — 更好地交互:Java 和 Thread 類。第 IV 部分 — Callable、Future 和朋友 更好地結合在一起:Java 和 Thread 類。第五部分 — 執行器、ThreadPool、Fork/Join
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION