java多執行緒(超詳細!)

小程xy發表於2024-07-29

Java 的多執行緒是一種允許在一個程式中同時執行多個執行緒的技術。每個執行緒是獨立的執行路徑,可以併發執行,從而提高程式的效率和響應能力。

1. 執行緒基礎

Java 中的執行緒可以透過繼承 Thread 類或實現 Runnable 介面來建立和管理。

1.1 繼承 Thread

class MyThread extends Thread {
    public void run() {
        System.out.println("Thread is running...");
    }
}

public class TestThread {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start(); // 啟動執行緒
    }
}

run() 方法包含執行緒執行的程式碼,而 start() 方法用於啟動新執行緒。

1.2 實現 Runnable 介面

class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Thread is running...");
    }
}

public class TestRunnable {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        t1.start(); // 啟動執行緒
    }
}

透過實現 Runnable 介面的方式可以更靈活地共享資源。

2. 執行緒的生命週期

執行緒在其生命週期中經歷以下狀態:

  • 新建(New): 執行緒物件被建立,但還沒有呼叫 start() 方法。
  • 就緒(Runnable): 執行緒物件呼叫了 start() 方法,等待 CPU 排程。
  • 執行(Running): 執行緒獲得 CPU,開始執行 run() 方法的程式碼。
  • 阻塞(Blocked): 執行緒因為某種原因(如等待資源、睡眠)被掛起。
  • 死亡(Dead): 執行緒執行完 run() 方法,或者因異常退出。

3. 執行緒控制

Java 提供了一些方法來控制執行緒的執行:

  • sleep(long millis):讓當前執行緒睡眠指定的毫秒數。
  • join():等待該執行緒終止,也就是說等待當前的執行緒結束, 才會繼續執行下面的程式碼
  • yield():暫停當前執行緒,讓出 CPU 給其他執行緒。
  • interrupt():中斷執行緒。

4. 執行緒同步

多執行緒程式中可能會出現多個執行緒同時訪問共享資源的情況,導致資料不一致的問題。為了解決這個問題,可以使用同步技術。

4.1. ReentrantLock

ReentrantLock 提供了比 synchronized 更加靈活的鎖機制,可以顯式地鎖定和解鎖。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private final Lock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        return count;
    }
}

4.2. ReadWriteLock

ReadWriteLock 提供了一對鎖,一個用於讀操作,一個用於寫操作。這允許多個讀執行緒同時訪問共享資源,但在寫執行緒訪問時會獨佔鎖。

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private int count = 0;

    public void increment() {
        lock.writeLock().lock();
        try {
            count++;
        } finally {
            lock.writeLock().unlock();
        }
    }

    public int getCount() {
        lock.readLock().lock();
        try {
            return count;
        } finally {
            lock.readLock().unlock();
        }
    }
}

4.3 synchronized

在方法前使用 synchronized 關鍵字,確保同一時間只有一個執行緒可以執行該方法。

class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

4.4 同步塊

同步塊可以更靈活地控制需要同步的程式碼塊,而不是整個方法。

class Counter {
    private int count = 0;

    public void increment() {
        synchronized (this) {
            count++;
        }
    }

    public int getCount() {
        return count;
    }
}

4.5 注意事項

下面程式碼中的寫法, 不能保證同一時間只有一個執行緒可以執行該方法。

因為 4.3 和 4.4 中 synchronized 的寫法是根據 this 來保證同一時間只有一個執行緒可以執行, 但是他們的 this 是不同的。(把 "需要替換的" 換成註釋上的就可以 "保證同一時間只有一個執行緒可以執行")

class Worker implements Runnable {
    public static int cnt = 0;

    @Override
    public synchronized void run() {
        for (int i = 0; i < 100000; i ++) {
            cnt ++;
        }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
    
//        Worker worker = new Worker();
//        Thread t1 = new Thread(worker);
//        Thread t2 = new Thread(worker);

        Thread t1 = new Thread(new Worker());	// 需要替換
        Thread t2 = new Thread(new Worker());	// 需要替換

        t1.start();
        t2.start();

        t1.join();
        t2.join();
        System.out.println(Worker.cnt);
    }
}

當然, 4.4 的程式碼也可以寫成下面這樣,這樣他就是根據 lock 這個物件來保證同步的, java中的物件都可以當作lock

class Counter {
	public static final Object lock = new Object();
    private int count = 0;

    public void increment() {
        synchronized (lock ) {
            count++;
        }
    }

    public int getCount() {
        return count;
    }
}

5. 執行緒通訊

Java 提供了 wait(), notify(), notifyAll() 方法來實現執行緒間的通訊。

  • wait():讓當前執行緒等待,直到其他執行緒呼叫 notify()notifyAll()
  • notify():喚醒一個正在等待的執行緒。
  • notifyAll():喚醒所有正在等待的執行緒。

6. 執行緒池

使用執行緒池可以有效地管理和複用執行緒,減少建立和銷燬執行緒的開銷。Java 提供了 Executor 框架來管理執行緒池。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestThreadPool {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            executor.execute(new MyRunnable());
        }
        executor.shutdown();
    }
}

7. 高階執行緒工具

Java 提供了很多高階執行緒工具,如 Semaphore, CountDownLatch, CyclicBarrier 等,用於複雜的執行緒協調和同步。

7.1 Semaphore

訊號量控制同時訪問特定資源的執行緒數量。

import java.util.concurrent.Semaphore;

public class TestSemaphore {
    private static final Semaphore semaphore = new Semaphore(3);

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new Task()).start();
        }
    }

    static class Task implements Runnable {
        public void run() {
            try {
                semaphore.acquire();
                System.out.println("Thread " + Thread.currentThread().getName() + " is accessing the resource.");
                Thread.sleep(2000);
                semaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

7.2 CountDownLatch

允許一個或多個執行緒等待,直到在其他執行緒中執行的一組操作完成。

import java.util.concurrent.CountDownLatch;

public class TestCountDownLatch {
    private static final int THREAD_COUNT = 3;
    private static final CountDownLatch latch = new CountDownLatch(THREAD_COUNT);

    public static void main(String[] args) {
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(new Task()).start();
        }
        try {
            latch.await(); // 主執行緒等待所有子執行緒完成
            System.out.println("All threads have finished.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class Task implements Runnable {
        public void run() {
            System.out.println("Thread " + Thread.currentThread().getName() + " is working.");
            latch.countDown(); // 計數器減一
        }
    }
}

結論

Java 多執行緒是一個強大且複雜的技術,需要深入理解和小心使用,以避免潛在的併發問題和死鎖情況。透過合理地利用執行緒同步、執行緒通訊和執行緒池等工具,可以編寫高效且安全的多執行緒應用程式。

相關文章