Java 中的多線程是通過并發編程來提高應用程序的效率和響應速度。Java 提供了多個機制和類來支持多線程編程,包括繼承 Thread
類、實現 Runnable
接口、使用線程池等。以下是 Java 中一些常見的多線程操作和應用場景。
1. 創建線程
1.1 通過繼承 Thread
類創建線程
繼承 Thread
類并重寫 run
方法是創建線程的一種方式。run
方法包含線程的執行體。
public class MyThread extends Thread {@Overridepublic void run() {System.out.println("Thread is running: " + Thread.currentThread().getName());}public static void main(String[] args) {MyThread thread = new MyThread();thread.start(); // 啟動線程}
}
start()
方法會啟動線程并調用run()
方法。run()
方法不能直接調用,必須通過start()
啟動線程。
1.2 通過實現 Runnable
接口創建線程
Runnable
接口適合當你不想繼承 Thread
類時使用。你只需要實現 run
方法,然后將其傳遞給 Thread
對象。
public class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("Thread is running: " + Thread.currentThread().getName());}public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable);thread.start(); // 啟動線程}
}
2. 線程的生命周期
線程有幾個常見的生命周期狀態:
- 新建(New):線程對象被創建,但未調用
start()
方法。 - 可運行(Runnable):線程被啟動,處于可運行狀態,但可能被操作系統調度暫停。
- 阻塞(Blocked):線程正在等待某個資源。
- 等待(Waiting):線程正在等待其他線程執行某些操作。
- 終止(Terminated):線程執行完畢,生命周期結束。
3. 線程的同步
當多個線程訪問共享資源時,為了避免數據不一致的問題,通常需要使用同步機制。
3.1 使用 synchronized
關鍵字
synchronized
關鍵字用于在方法或代碼塊上加鎖,確保在同一時間內只有一個線程能夠執行被同步的代碼塊。
示例:同步方法
public class Counter {private int count = 0;// 使用 synchronized 修飾方法,保證線程安全public synchronized void increment() {count++;}public int getCount() {return count;}public static void main(String[] args) {Counter counter = new Counter();// 創建兩個線程同時訪問共享資源Thread t1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});t1.start();t2.start();try {t1.join(); // 等待線程 t1 執行完畢t2.join(); // 等待線程 t2 執行完畢} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Final count: " + counter.getCount()); // 輸出: Final count: 2000}
}
在上面的例子中,increment
方法被 synchronized
修飾,這意味著同一時刻只有一個線程能夠訪問這個方法。
3.2 使用同步代碼塊
你也可以使用同步代碼塊來鎖定指定的代碼區域,從而減少鎖的范圍,提高效率。
public class Counter {private int count = 0;public void increment() {synchronized (this) { // 鎖定當前對象count++;}}public int getCount() {return count;}public static void main(String[] args) {Counter counter = new Counter();Thread t1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});t1.start();t2.start();try {t1.join(); // 等待線程 t1 執行完畢t2.join(); // 等待線程 t2 執行完畢} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Final count: " + counter.getCount()); // 輸出: Final count: 2000}
}
在上面的代碼中,increment
方法內的同步代碼塊確保了每次只有一個線程能夠進入該代碼塊,防止了競態條件的出現。
4. 線程間通信
線程間通信是在多個線程之間交換信息的機制。Java 提供了 wait()
、notify()
和 notifyAll()
方法來實現線程間的通信。
4.1 使用 wait()
和 notify()
進行線程通信
wait()
:使當前線程進入等待狀態,并釋放鎖,直到被其他線程通知。notify()
:通知一個正在等待的線程,使其從等待狀態中醒來,繼續執行。notifyAll()
:通知所有正在等待的線程,所有線程都會嘗試重新獲取鎖。
示例:生產者-消費者問題
class Storage {private int product = 0;private final int capacity = 10;// 生產者生產產品public synchronized void produce() throws InterruptedException {while (product >= capacity) {wait(); // 如果庫存已滿,生產者等待}product++;System.out.println("Produced, product count: " + product);notifyAll(); // 通知消費者線程}// 消費者消費產品public synchronized void consume() throws InterruptedException {while (product <= 0) {wait(); // 如果庫存為空,消費者等待}product--;System.out.println("Consumed, product count: " + product);notifyAll(); // 通知生產者線程}
}public class ProducerConsumer {public static void main(String[] args) {Storage storage = new Storage();// 生產者線程Thread producer = new Thread(() -> {try {for (int i = 0; i < 20; i++) {storage.produce();Thread.sleep(100); // 模擬生產時間}} catch (InterruptedException e) {e.printStackTrace();}});// 消費者線程Thread consumer = new Thread(() -> {try {for (int i = 0; i < 20; i++) {storage.consume();Thread.sleep(150); // 模擬消費時間}} catch (InterruptedException e) {e.printStackTrace();}});producer.start();consumer.start();}
}
在這個例子中,我們模擬了生產者和消費者的線程通信機制。生產者線程不斷生產產品,而消費者線程不斷消費產品。當產品庫存滿時,生產者等待;當產品庫存為空時,消費者等待。
5. 線程池(Executor Service)
線程池是 Java 提供的一個高效的多線程管理工具,它可以避免創建過多的線程,減少系統資源的消耗。Java 中的線程池是通過 ExecutorService
接口來管理的。
5.1 創建線程池
線程池的創建通常使用 Executors
工廠類,它提供了幾種常用的線程池:
newFixedThreadPool(int n)
:創建一個固定大小的線程池。
示例:使用 ExecutorService
執行任務
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPoolExample {public static void main(String[] args) {// 創建一個固定大小的線程池,池中有 3 個線程ExecutorService executorService = Executors.newFixedThreadPool(3);// 提交多個任務給線程池for (int i = 0; i < 5; i++) {executorService.submit(() -> {System.out.println("Task executed by: " + Thread.currentThread().getName());try {Thread.sleep(1000); // 模擬任務執行} catch (InterruptedException e) {e.printStackTrace();}});}// 關閉線程池executorService.shutdown(); // 提交完任務后調用 shutdown 來關閉線程池}
}
6. 線程的優先級
Java 允許設置線程的優先級,從而影響線程的調度順序。線程的優先級是一個整數值,范圍從 1 到 10,其中 1 為最低優先級,10 為最高優先級。
示例:設置線程優先級
public class ThreadPriority {public static void main(String[] args) {Thread highPriorityThread = new Thread(() -> {System.out.println("High priority thread is running.");});Thread lowPriorityThread = new Thread(() -> {System.out.println("Low priority thread is running.");});highPriorityThread.setPriority(Thread.MAX_PRIORITY); // 設置高優先級lowPriorityThread.setPriority(Thread.MIN_PRIORITY); // 設置低優先級highPriorityThread.start();lowPriorityThread.start();}
}
注意,雖然 Java 提供了線程優先級的設置,但線程調度是由操作系統管理的,不同的操作系統可能會根據自己的調度算法來決定線程的實際執行順序。
7. 中斷線程
線程的中斷通常用于停止線程的執行或通知線程需要停止。通過調用線程的 interrupt()
方法可以設置線程的中斷標志,而線程可以通過 isInterrupted()
方法來檢查自己是否被中斷。需要注意的是,interrupt()
并不會立即終止線程,它只是設置線程的中斷狀態,具體的中斷行為需要在線程代碼中自行判斷并處理。
示例:中斷線程
public class InterruptExample {public static void main(String[] args) throws InterruptedException {Thread longRunningThread = new Thread(() -> {try {for (int i = 0; i < 10; i++) {if (Thread.interrupted()) {System.out.println("Thread is interrupted, stopping...");return; // 響應中斷,終止線程}System.out.println("Running... " + i);Thread.sleep(1000); // 模擬長時間任務}} catch (InterruptedException e) {System.out.println("Thread was interrupted during sleep.");}});longRunningThread.start();// 等待 3 秒后中斷線程Thread.sleep(3000);longRunningThread.interrupt(); // 發出中斷信號}
}
8. 線程的 join()
方法
join()
方法用于等待一個線程完成。當調用 join()
方法時,當前線程會阻塞,直到目標線程執行完畢為止。它常用于確保某些任務執行完之后再執行其他任務。
示例:使用 join()
等待線程完成
public class JoinExample {public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {try {Thread.sleep(2000); // 模擬任務執行System.out.println("Thread 1 finished");} catch (InterruptedException e) {e.printStackTrace();}});Thread thread2 = new Thread(() -> {try {Thread.sleep(1000); // 模擬任務執行System.out.println("Thread 2 finished");} catch (InterruptedException e) {e.printStackTrace();}});thread1.start();thread2.start();// 等待 thread1 和 thread2 完成后再繼續執行thread1.join();thread2.join();System.out.println("All threads finished");}
}
在這個例子中,main
線程會等待 thread1
和 thread2
執行完畢后再繼續執行。