天天看點

多線程系列-建立方式 線程同步,通信,線程池

作者:Java機械師

介紹

在計算機程式中,線程是一種重要的并發執行方式。通過建立線程,可以在一個程式中同時執行多個任務,提高程式的并發處理能力和響應性能。在Java語言中,建立線程的方式有三種:繼承Thread類、實作Runnable接口和使用Callable接口,簡單了解一下 線程同步 線程通信 線程池 等概念

多線程系列-建立方式 線程同步,通信,線程池

繼承Thread類

繼承Thread類是Java語言中最原始、最簡單的建立線程的方式。該方式的基本思路是定義一個類,該類繼承Thread類,并重寫Thread類中的run()方法。run()方法中包含了線程要執行的代碼,當線程啟動時,run()方法就會被調用,線程就開始執行。

下面是繼承Thread類建立線程的示例代碼:

public class MyThread extends Thread {
    @Override
    public void run() {
        // 線程要執行的代碼
    }
}

// 建立線程
MyThread thread = new MyThread();

// 啟動線程
thread.start();
複制代碼           

繼承Thread類建立線程的優點是簡單易用,缺點是不夠靈活。由于Java中不支援多重繼承,是以如果一個類已經繼承了其他類,則無法再通過繼承Thread類來建立線程。另外,繼承Thread類還存在一些局限性,例如無法共享代碼等。

實作Runnable接口(線程任務)

實作Runnable接口是Java語言中建立線程的推薦方式。該方式的基本思路是定義一個類,該類實作Runnable接口,并實作run()方法。run()方法中包含了線程要執行的代碼,當線程啟動時,run()方法就會被調用,線程就開始執行。

下面是實作Runnable接口建立線程的示例代碼:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 線程要執行的代碼
    }
}

// 建立線程
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);

// 啟動線程
thread.start();
複制代碼           

實作Runnable接口建立線程的優點是更靈活,缺點是稍微複雜一些。由于Java中支援多重接口實作,是以即使一個類已經繼承了其他類,也可以通過實作Runnable接口來建立線程。另外,實作Runnable接口還可以提高代碼的複用性,因為多個線程可以共享同一個Runnable對象,進而共享同一個代碼塊。

使用Callable接口(線程任務)

使用Callable接口是Java語言中建立線程的進階方式。與實作Runnable接口不同的是,Callable接口的call()方法可以傳回一個結果,而且可以抛出異常。使用Callable接口建立線程的步驟如下:

  1. 定義一個類,該類實作Callable接口,并實作call()方法。 2. 建立一個ExecutorService對象,該對象可以管理多個線程。
  2. 建立一個Callable對象,将其送出給ExecutorService對象。
  3. 擷取Future對象,該對象可以擷取線程執行的結果。

下面是使用Callable接口建立線程的示例代碼:

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        // 線程要執行的代碼
        return "result";
    }
}

// 建立ExecutorService對象
ExecutorService executorService = Executors.newSingleThreadExecutor();

// 建立Callable對象,并送出給ExecutorService對象
MyCallable callable = new MyCallable();
Future<String> future = executorService.submit(callable);

// 擷取線程執行的結果
String result = future.get();

// 關閉ExecutorService對象
executorService.shutdown();
複制代碼           

使用Callable接口建立線程的優點是更加靈活,可以傳回結果或抛出異常。缺點是稍微複雜一些,需要使用ExecutorService對象來管理線程。另外,使用Callable接口還可以通過調用Future對象的get()方法來擷取線程執行的結果,而且get()方法可以設定逾時時間,進而避免線程阻塞。

線程同步

在多線程程式中,線程同步是一種重要的機制,可以保證多個線程通路共享資源時不會發生沖突。Java語言提供了多種線程同步機制,包括synchronized關鍵字、ReentrantLock類、Semaphore類等。下面以synchronized關鍵字為例,介紹線程同步的實作方法。

在Java語言中,synchronized關鍵字可以用來修飾方法或代碼塊,進而實作線程同步。當一個線程進入synchronized修飾的方法或代碼塊時,該方法或代碼塊就會被鎖定,其他線程無法通路,直到目前線程執行完畢釋放鎖為止。

下面是使用synchronized關鍵字實作線程同步的示例代碼:

public class MyRunnable implements Runnable {
    private int count = 0;

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

    public int getCount() {
        return count;
    }
}

// 建立線程
MyRunnable runnable = new MyRunnable();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);

// 啟動線程
thread1.start();
thread2.start();

// 等待線程執行完畢
thread1.join();
thread2.join();

// 擷取結果
int result = runnable.getCount();
複制代碼           

在上面的示例代碼中,MyRunnable類實作了Runnable接口,并重寫了run()方法。在run()方法中,使用synchronized關鍵字對整個方法進行了鎖定,進而實作了線程同步。當多個線程同時調用該方法時,隻有一個線程能夠進入,其他線程會被阻塞,直到目前線程執行完畢釋放鎖為止。

線程通信

線上程程式中,線程之間需要進行通信才能完成複雜的任務。Java語言提供了多種線程通信機制,包括wait()、notify()、notifyAll()等。其中,wait()方法用于将目前線程挂起,直到其他線程調用notify()或notifyAll()方法喚醒它;notify()方法用于喚醒一個等待的線程;notifyAll()方法用于喚醒所有等待的線程。

下面以wait()、notify()、notifyAll()方法為例,介紹線程通信的實作方法。

在Java語言中,wait()、notify()、notifyAll()方法隻能在synchronized修飾的代碼塊或方法中使用,因為這些方法都需要擷取鎖。在調用wait()方法時,目前線程會被挂起,并釋放鎖;在調用notify()或notifyAll()方法時,其他等待的線程會被喚醒,并嘗試重新擷取鎖。需要注意的是,wait()、notify()、notifyAll()方法隻能用于同步代碼塊或方法中的對象上,否則會抛出IllegalMonitorStateException異常。

下面是使用wait()、notify()、notifyAll()方法實作線程通信的示例代碼:

public class MyRunnable implements Runnable {
    private Object lock = new Object();
    private boolean flag = false;

    @Override
    public void run() {
        synchronized (lock) {
            while (!flag) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 線程要執行的代碼
        }
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
        synchronized (lock) {
            lock.notify();
        }
    }
}

// 建立線程
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);

// 啟動線程
thread.start();

// 通知線程執行任務
runnable.setFlag(true);
複制代碼           

在上面的示例代碼中,MyRunnable類實作了Runnable接口,并重寫了run()方法。在run()方法中,使用while循環和wait()方法實作了線程挂起和喚醒的邏輯。在setFlag()方法中,調用了notify()方法喚醒等待的線程,并設定了flag變量的值,進而觸發線程執行任務。

線程池

在Java語言中,線程池是一種常見的線程管理機制,可以避免頻繁地建立和銷毀線程,提高程式的性能。Java語言提供了多種線程池實作,包括ThreadPoolExecutor類、ScheduledThreadPoolExecutor類等。其中,ThreadPoolExecutor類是最常用的線程池實作,可以設定核心線程數、最大線程數、等待隊列、拒絕政策等參數,進而靈活地管理線程。

下面以ThreadPoolExecutor類為例,介紹線程池的實作方法。

在Java語言中,使用ThreadPoolExecutor類可以建立一個線程池。ThreadPoolExecutor類的構造方法可以設定核心線程數、最大線程數、等待隊列、拒絕政策等參數,如下所示:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    // 省略具體實作
}
複制代碼           

其中,corePoolSize參數表示核心線程數;maximumPoolSize參數表示最大線程數;keepAliveTime參數表示線程的空閑時間;unit參數表示時間機關;workQueue參數表示等待隊列;handler參數表示拒絕政策。

下面是使用ThreadPoolExecutor類實作線程池的示例代碼:

public class MyRunnable implements Runnable {
    private String name;

    public MyRunnable(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        // 線程要執行的代碼
    }
}

// 建立線程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
        2, // 核心線程數
        4, // 最大線程數
        60, // 線程空閑時間
        TimeUnit.SECONDS, // 時間機關
        new ArrayBlockingQueue<>(100), // 等待隊列
        new ThreadPoolExecutor.AbortPolicy() // 拒絕政策
);

// 添加任務到線程池
for (int i = 0; i < 10; i++) {
    MyRunnable runnable = new MyRunnable("Thread-" + i);
    executor.execute(runnable);
}

// 關閉線程池
executor.shutdown();
複制代碼           

在上面的示例代碼中,MyRunnable類實作了Runnable接口,并重寫了run()方法。在ThreadPoolExecutor類的構造方法中,設定了核心線程數、最大線程數、等待隊列和拒絕政策等參數。在for循環中,建立了10個MyRunnable對象,并調用了executor.execute()方法将它們添加到線程池中。最後,在主線程中調用executor.shutdown()方法關閉線程池。

繼承Thread類與實作Runnable接口的差別

在使用Java建立線程時,可以繼承Thread類或實作Runnable接口。它們的差別在于,繼承Thread類必須重寫run()方法,而實作Runnable接口需要在類中實作run()方法。是以,使用實作Runnable接口的方式,可以避免因為Java的單繼承機制而無法繼承其他類的問題。此外,使用實作Runnable接口的方式,可以将任務和線程分離,更加靈活。

線程同步機制

在Java多線程程式設計中,線程同步機制用于協調不同線程之間的操作,以確定資料的正确性。Java提供了多種線程同步機制,包括synchronized關鍵字、Lock接口、ReentrantLock類、ReadWriteLock接口等。其中,synchronized關鍵字是Java中最基本的同步機制,可以用于方法或代碼塊的同步,也可以用于靜态方法或類的同步。Lock接口是Java 5引入的新特性,相比于synchronized關鍵字,Lock接口提供了更多的功能和更細粒度的控制。ReentrantLock類是Lock接口的一種實作,ReadWriteLock接口是Lock接口的一種擴充,提供了讀寫鎖的功能,可以更好地支援多線程讀寫操作。

線程通信機制

Java中的線程通信機制用于協調不同線程之間的協作,以確定線程之間的正确執行順序和資料的正确性。Java提供了多種線程通信機制,包括wait()、notify()、notifyAll()等方法,以及Condition接口。其中,wait()方法用于使目前線程等待,直到其他線程調用notify()或notifyAll()方法喚醒它;notify()方法用于喚醒等待在對象上的一個線程;notifyAll()方法用于喚醒等待在對象上的所有線程。Condition接口是Java 5引入的新特性,提供了更靈活的線程通信機制,可以實作更複雜的線程協作。

線程池的優點

使用線程池可以優化多線程程式設計的性能和可靠性。線程池通過管理和複用線程,可以減少線程的建立和銷毀開銷,提高系統的資源使用率。此外,線程池可以控制線程的數量和運作狀态,避免因為線程數量過多而導緻系統負荷過大的問題。另外,線程池還可以提供線程的複用和錯誤處理機制,提高程式的可靠性和穩定性。

線程池

Java提供了Executor架構來管理線程池,可以通過Executors工廠類建立不同類型的線程池,包括FixedThreadPool、CachedThreadPool、SingleThreadExecutor等。其中,FixedThreadPool用于建立固定大小的線程池,CachedThreadPool用于建立可變大小的線程池,SingleThreadExecutor用于建立隻有一個線程的線程池。

建立線程池時需要注意一些細節,比如線程池的大小應該根據系統的資源和應用程式的需求來選擇,不要設定過大或過小;線程池的建立應該在應用程式初始化時完成,而不是在需要使用時建立;線程池的關閉需要謹慎處理,避免因為線程狀态不正确而導緻系統異常等問題。

線程安全與可見性

在多線程程式設計中,線程安全和可見性是非常重要的概念。線程安全是指多個線程同時通路同一個對象時,不會出現資料競争或不一緻的問題。線程可見性是指一個線程對共享變量的修改,對其他線程是可見的。Java提供了多種機制來實作線程安全和可見性,包括volatile關鍵字、synchronized關鍵字、Lock接口等。

在Java中,volatile關鍵字用于保證變量的可見性,可以用于修飾變量或對象引用。synchronized關鍵字用于實作方法或代碼塊的同步,可以保證代碼的原子性和可見性。Lock接口可以實作更細粒度的鎖控制和線程同步。

線程的生命周期

在Java中,線程的生命周期包括5個階段:建立狀态、就緒狀态、運作狀态、阻塞狀态和終止狀态。線程的生命周期由Thread類的方法來管理,包括start()、run()、yield()、sleep()、wait()、join()和interrupt()等方法。

其中,start()方法用于啟動線程;run()方法用于執行線程任務;yield()方法用于讓出CPU,讓其他線程執行;sleep()方法用于使目前線程暫停一段時間;wait()方法用于使線程等待,直到其他線程調用notify()或notifyAll()方法喚醒它;join()方法用于等待其他線程執行完畢;interrupt()方法用于中斷線程的執行。

總結

Java中建立線程是多線程程式設計的重要部分,需要注意線程的同步、通信、安全、可見性等問題,以確定程式的正确性和可靠性。線程池可以優化多線程程式設計的性能和可靠性,需要注意線程池的建立和關閉。線程的生命周期包括5個階段,需要通過Thread類的方法來管理。通過合理使用線程的機制和技術,可以提高程式的除了線程同步、通信、安全和可見性等問題外,還需要注意線程的排程和優先級,以確定線程之間的公平性和有效性。線程排程的機制包括時間片輪轉、優先級排程和分時排程等,需要根據具體情況選擇合适的排程政策和算法。

另外,多線程程式設計還需要注意資源的競争和互斥通路,如共享變量、共享檔案、共享記憶體等。為了避免資源沖突和資料不一緻性問題,可以采用鎖、信号量、管程和原子操作等同步機制來保證線程的安全性和正确性。

總之,多線程程式設計是一項複雜而重要的技術,需要深入了解線程的機制和特性,靈活運用線程的機制和技術,以提高程式的效率和可靠性

繼續閱讀