農行一面:如何保證線程T1,T2,T3 順序執行?
線程是 Java執行的最小單元,通常意義上來說,多個線程是為了加快速度且無需保序,這篇文章,我們來分析一道農業銀行的面試題目:如要保證線程T1, T2, T3順序執行?
考察意圖
在面試中出現這道問題,通常是為了考察候選人的以下幾個知識點:
- 多線程基礎知識:希望了解候選人是否熟悉Java多線程的基本概念,包括線程的創建、啟動和同步機制。
- 同步機制的理解:候選人需要展示對Java中各種同步工具的理解,如join()、CountDownLatch、Semaphore、CyclicBarrier等,并知道如何在不同場景下應用這些工具。
- 線程間通信:希望候選人理解線程間通信的基本原理,例如如何使用wait()和notify()來協調線程。
- 對Java并發包的熟悉程度:希望候選人了解Java并發包(java.util.concurrent)中的工具和類,展示其對現代Java并發編程的掌握。
保證線程順序執行的方法
在分析完面試題的考察意圖之后,我們再分析如何保證線程順序執行,這里列舉了幾種常見的方式。
1.join()
join()方法是Thread類的一部分,可以讓一個線程等待另一個線程完成執行。當你在一個線程T上調用T.join()時,調用線程將進入等待狀態,直到線程T完成(即終止)。因此,可以通過在每個線程啟動后調用join()來實現順序執行。
如下示例代碼,展示了join()如何保證線程順序執行:
Thread t1 = new Thread(() -> {
// 線程T1的任務
});
Thread t2 = new Thread(() -> {
// 線程T2的任務
});
Thread t3 = new Thread(() -> {
// 線程T3的任務
});
t1.start();
t1.join(); // 等待t1完成
t2.start();
t2.join(); // 等待t2完成
t3.start();
t3.join(); // 等待t3完成
2.CountDownLatch
CountDownLatch通過一個計數器來實現,初始時,計數器的值由構造函數設置,每次調用countDown()方法,計數器的值減1。當計數器的值變為零時,所有等待在await()方法上的線程都將被喚醒,繼續執行。
CountDownLatch是Java并發包(java.util.concurrent)中的一個同步輔助類,用于協調多個線程之間的執行順序。它允許一個或多個線程等待另外一組線程完成操作。
如下示例代碼,展示了CountDownLatch如何保證線程順序執行:
CountDownLatch latch1 = new CountDownLatch(1);
CountDownLatch latch2 = new CountDownLatch(1);
Thread t1 = new Thread(() -> {
// 線程T1的任務
latch1.countDown(); // 完成后遞減latch1
});
Thread t2 = new Thread(() -> {
try {
latch1.await(); // 等待T1完成
// 線程T2的任務
latch2.countDown(); // 完成后遞減latch2
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread t3 = new Thread(() -> {
try {
latch2.await(); // 等待T2完成
// 線程T3的任務
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
t1.start();
t2.start();
t3.start();
CountDownLatch關鍵方法解析:
- CountDownLatch(int count) : 構造函數,創建一個CountDownLatch實例,計數器的初始值為count。
- void await() : 使當前線程等待,直到計數器的值變為零。
- boolean await(long timeout, TimeUnit unit) : 使當前線程等待,直到計數器的值變為零或等待時間超過指定的時間。
- void countDown() : 遞減計數器的值。當計數器的值變為零時,所有等待的線程被喚醒。
3.Semaphore
Semaphore通過一個計數器來管理許可,計數器的初始值由構造函數指定,表示可用許可的數量。線程可以通過調用acquire()方法請求許可,如果許可可用則授予訪問權限,否則線程將阻塞。使用完資源后,線程調用release()方法釋放許可,從而允許其他阻塞的線程獲取許可。
如下示例代碼,展示了Semaphore如何保證線程順序執行:
Semaphore semaphore1 = new Semaphore(0);
Semaphore semaphore2 = new Semaphore(0);
Thread t1 = new Thread(() -> {
// 線程T1的任務
semaphore1.release(); // 釋放一個許可
});
Thread t2 = new Thread(() -> {
try {
semaphore1.acquire(); // 獲取許可,等待T1完成
// 線程T2的任務
semaphore2.release(); // 釋放一個許可
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread t3 = new Thread(() -> {
try {
semaphore2.acquire(); // 獲取許可,等待T2完成
// 線程T3的任務
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
t1.start();
t2.start();
t3.start();
Semaphore關鍵方法分析:
- Semaphore(int permits) :構造一個具有給定許可數的Semaphore。
- Semaphore(int permits, boolean fair) :構造一個具有給定許可數的Semaphore,并指定是否是公平的。公平性指的是線程獲取許可的順序是否是先到先得。
- void acquire() :獲取一個許可,如果沒有可用許可,則阻塞直到有許可可用。
- void acquire(int permits) :獲取指定數量的許可。
- void release() :釋放一個許可。
- void release(int permits) :釋放指定數量的許可。
- int availablePermits() :返回當前可用的許可數量。
- boolean tryAcquire() :嘗試獲取一個許可,立即返回true或false。
- boolean tryAcquire(long timeout, TimeUnit unit) :在給定的時間內嘗試獲取一個許可。
4.單線程池
單線程池(Executors.newSingleThreadExecutor())可以確保任務按提交順序依次執行。所有任務都會在同一個線程中運行,保證了順序性。
如下示例代碼展示了單線程池如何保證線程順序執行:
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(new T1());
executor.submit(new T2());
executor.submit(new T3());
executor.shutdown();
單線程這種方法簡單易用,適合需要順序執行的場景。
5.synchronized
synchronized 是Java中的一個關鍵字,用于實現線程同步,確保多個線程對共享資源的訪問是互斥的。它通過鎖機制來保證同一時刻只有一個線程可以執行被Synchronized保護的代碼塊,從而避免數據不一致和線程安全問題。
如下示例代碼,展示了synchronized如何保證線程順序執行:
class Task {
synchronized void executeTask(String taskName) {
System.out.println(taskName + " 執行");
}
}
public class Main {
public static void main(String[] args) {
Task task = new Task();
new Thread(() -> task.executeTask("T1")).start();
new Thread(() -> task.executeTask("T2")).start();
new Thread(() -> task.executeTask("T3")).start();
}
}
總結
在這篇文章中,我們分析了 5種保證線程順序執行的方法,依次如下:
- join()
- CountDownLatch
- Semaphore
- 單線程池
- synchronized
在實際開發中,需要在業務代碼中去保證線程執行順序的情況幾乎不會出現,因此,這個面試題其實缺乏實際的應用場景,純粹是為了面試存在。盡管是面試題,還是可以幫助我們更好地去了解和掌握線程。