多線程打印奇偶數,怎么控制打印的順序
可以利用wait()和notify()來控制線程的執行順序。
以下是一個基于這種方法的簡單示例:
public class PrintOddEven {private static final Object lock = new Object();private static int count = 1;private static final int MAX_COUNT = 10;public static void main(String[] args) {Runnable printOdd = () -> {synchronized (lock) {while (count <= MAX_COUNT) {if (count % 2 != 0) {System.out.println(Thread.currentThread().getName() + ": " + count++);lock.notify();} else {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}};Runnable printEven = () -> {synchronized (lock) {while (count <= MAX_COUNT) {if (count % 2 == 0) {System.out.println(Thread.currentThread().getName() + ": " + count++);lock.notify();} else {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}};Thread oddThread = new Thread(printOdd, "OddThread");Thread evenThread = new Thread(printEven, "EvenThread");oddThread.start();evenThread.start();}
}
在上面的示例中,通過一個共享的鎖對象lock來控制兩個線程的交替執行。一個線程負責打印奇數,另一個線程負責打印偶數,通過wait()和notify()方法來在兩個線程之間實現順序控制。當當前應該打印奇數時,偶數線程會進入等待狀態,反之亦然。
- 創建 3 個并發執行的線程,在每個線程的任務結束時調用?
countDown
?方法將計數器減 1。 - 創建第 4 個線程,使用?
await
?方法等待計數器為 0,即等待其他 3 個線程完成任務。
import java.util.concurrent.CountDownLatch;public class CountDownLatchExample {public static void main(String[] args) {// 創建一個 CountDownLatch,初始計數為 3CountDownLatch latch = new CountDownLatch(3);// 創建并啟動 3 個并發線程for (int i = 0; i < 3; i++) {final int threadNumber = i + 1;new Thread(() -> {try {System.out.println("Thread " + threadNumber + " is working.");// 模擬線程執行任務Thread.sleep((long) (Math.random() * 1000));System.out.println("Thread " + threadNumber + " has finished.");} catch (InterruptedException e) {e.printStackTrace();} finally {// 任務完成后,計數器減 1latch.countDown();}}).start();}// 創建并啟動第 4 個線程,等待其他 3 個線程完成new Thread(() -> {try {System.out.println("Waiting for other threads to finish.");// 等待計數器為 0latch.await();System.out.println("All threads have finished, this thread starts to work.");} catch (InterruptedException e) {e.printStackTrace();}}).start();}
}
代碼解釋:
- 首先,創建了一個?
CountDownLatch
?對象?latch
,并將其初始計數設置為 3。 - 然后,使用?
for
?循環創建并啟動 3 個線程。每個線程會執行一些工作(這里使用?Thread.sleep
?模擬),在工作完成后,會調用?latch.countDown()
?方法,將?latch
?的計數減 1。 - 最后,創建第 4 個線程。這個線程在開始時調用?
latch.await()
?方法,它會阻塞,直到?latch
?的計數為 0,即前面 3 個線程都調用了?countDown()
?方法。一旦計數為 0,該線程將繼續執行后續任務。
#單例模型既然已經用了synchronized,為什么還要在加volatile?
使用?synchronized
?和?volatile
?一起,可以創建一個既線程安全又能正確初始化的單例模式,避免了多線程環境下的各種潛在問題。這是一種比較完善的線程安全的單例模式實現方式,尤其適用于高并發環境。
public class Singleton {private static volatile Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}
synchronized
?關鍵字的作用用于確保在多線程環境下,只有一個線程能夠進入同步塊(這里是?synchronized (Singleton.class)
)。在創建單例對象時,通過?synchronized
?保證了創建過程的線程安全性,避免多個線程同時創建多個單例對象。
volatile
?確保了對象引用的可見性和創建過程的有序性,避免了由于指令重排序而導致的錯誤。
instance = new Singleton();
?這行代碼并不是一個原子操作,它實際上可以分解為以下幾個步驟:
- 分配內存空間。
- 實例化對象。
- 將對象引用賦值給?
instance
。
由于 Java 內存模型允許編譯器和處理器對指令進行重排序,在沒有?volatile
?的情況下,可能會出現重排序,例如先將對象引用賦值給?instance
,但對象的實例化操作尚未完成。
這樣,其他線程在檢查?instance == null
?時,會認為單例已經創建,從而得到一個未完全初始化的對象,導致錯誤。
volatile
?可以保證變量的可見性和禁止指令重排序。它確保對?instance
?的修改對所有線程都是可見的,并且保證了上述三個步驟按順序執行,避免了在單例創建過程中因指令重排序而導致的問題。
#3個線程并發執行,1個線程等待這三個線程全部執行完在執行,怎么實現?
可以使用?CountDownLatch
?來實現 3 個線程并發執行,另一個線程等待這三個線程全部執行完再執行的需求。以下是具體的實現步驟:
- 創建一個?
CountDownLatch
?對象,并將計數器初始化為 3,因為有 3 個線程需要等待。 - 創建 3 個并發執行的線程,在每個線程的任務結束時調用?
countDown
?方法將計數器減 1。 - 創建第 4 個線程,使用?
await
?方法等待計數器為 0,即等待其他 3 個線程完成任務。
import java.util.concurrent.CountDownLatch;public class CountDownLatchExample {public static void main(String[] args) {// 創建一個 CountDownLatch,初始計數為 3CountDownLatch latch = new CountDownLatch(3);// 創建并啟動 3 個并發線程for (int i = 0; i < 3; i++) {final int threadNumber = i + 1;new Thread(() -> {try {System.out.println("Thread " + threadNumber + " is working.");// 模擬線程執行任務Thread.sleep((long) (Math.random() * 1000));System.out.println("Thread " + threadNumber + " has finished.");} catch (InterruptedException e) {e.printStackTrace();} finally {// 任務完成后,計數器減 1latch.countDown();}}).start();}// 創建并啟動第 4 個線程,等待其他 3 個線程完成new Thread(() -> {try {System.out.println("Waiting for other threads to finish.");// 等待計數器為 0latch.await();System.out.println("All threads have finished, this thread starts to work.");} catch (InterruptedException e) {e.printStackTrace();}}).start();}
}
代碼解釋:
- 首先,創建了一個?
CountDownLatch
?對象?latch
,并將其初始計數設置為 3。 - 然后,使用?
for
?循環創建并啟動 3 個線程。每個線程會執行一些工作(這里使用?Thread.sleep
?模擬),在工作完成后,會調用?latch.countDown()
?方法,將?latch
?的計數減 1。 - 最后,創建第 4 個線程。這個線程在開始時調用?
latch.await()
?方法,它會阻塞,直到?latch
?的計數為 0,即前面 3 個線程都調用了?countDown()
?方法。一旦計數為 0,該線程將繼續執行后續任務。
#假設兩個線程并發讀寫同一個整型變量,初始值為零,每個線程加 50 次,結果可能是什么?
在沒有任何同步機制的情況下,兩個線程并發對同一個整型變量進行 50 次加 1 操作,最終結果可能是 100,也可能小于 100,最壞的結果是 50,也就是最終的結果可能是在 [50, 100] 。
小于 100 情況的分析,由于對整型變量的?num++
?操作不是原子操作,它實際上包含了三個步驟:讀取變量的值、將值加 1、將新值寫回變量。在多線程環境下,可能會出現線程安全問題。例如,線程 1 和線程 2 同時讀取了變量的當前值,然后各自將其加 1,最后都將相同的新值寫回變量,這就導致了一次加 1 操作的丟失。這種情況會多次發生,最終結果就會小于 100。
import java.util.concurrent.atomic.AtomicInteger;public class AtomicIntegerAddition {private static AtomicInteger num = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {for (int i = 0; i < 50; i++) {num.incrementAndGet();}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 50; i++) {num.incrementAndGet();}});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println("最終結果: " + num.get());}
}
第二種方式:通過?synchronized
?關鍵字或?ReentrantLock
?確保操作的互斥性,代碼如下:
public class SynchronizedAddition {private static int num = 0;private static final Object lock = new Object();public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {for (int i = 0; i < 50; i++) {synchronized (lock) {num++;}}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 50; i++) {synchronized (lock) {num++;}}});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println("最終結果: " + num);}
}