Java 線程基礎


本文部分摘自《Java 併發編程的藝術》java


線程簡介

1. 什麼是線程?

現代操做系統在運行一個程序時,會爲其建立一個進程,一個進程裏能夠建立多個線程。現代操做系統調度的最小單元是線程,也叫輕量級進程。這些線程都擁有各自的計數器、堆棧和局部變量等屬性,而且能訪問共享的內存變量。處理器在這些線程上高速切換,讓使用者以爲這些線程在同時執行編程

2. 爲何使用多線程?

使用多線程的緣由主要有如下幾點:安全

  • 更多的處理器核心多線程

    經過使用多線程技術,將計算邏輯分配到多個處理器核心上,能夠顯著減小程序的處理時間併發

  • 更快的響應時間ide

    有時咱們會編寫一些較爲複雜的代碼(主要指業務邏輯),可使用多線程技術,將數據一致性不強的操做派發給其餘線程處理(也可使用消息隊列)。這樣作的好處是響應用戶請求的線程可以儘量快地處理完成,縮短了響應時間this

  • 更好的編程模型操作系統

    Java 已經爲多線程編程提供了一套良好的編程模型,開發人員只需根據問題須要創建合適的模型便可線程


線程優先級

現代操做系統基本採用時分的形式調度運行的線程,操做系統會分出一個個時間片,線程分配到若干時間片,當線程的時間片用完了發生線程調度,並等待下次分配。線程分配到的時間片多少也就決定了線程使用處理器資源的多少,而線程優先級就是決定線程須要多或少分配一些處理器資源的線程屬性code

在 Java 線程中,經過一個整型成員變量 priority 來控制優先級,優先級的範圍從 1 ~ 10,在線程構建時能夠經過 setPriority(int) 方法來修改優先級,默認優先級是 5,優先級高的線程分配時間片的數量要多於優先級低的線程。不過,在不一樣的 JVM 以及操做系統上,線程規劃會存在差別,有些操做系統甚至會忽略線程優先級的設定

public class Priority {

    private static volatile boolean notStart = true;
    private static volatile boolean notEnd = true;

    public static void main(String[] args) throws Exception {
        List<Job> jobs = new ArrayList<Job>();
        for (int i = 0; i < 10; i++) {
            int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;
            Job job = new Job(priority);
            jobs.add(job);
            Thread thread = new Thread(job, "Thread:" + i);
            thread.setPriority(priority);
            thread.start();
        }
        notStart = false;
        TimeUnit.SECONDS.sleep(10);
        notEnd = false;
        for (Job job : jobs) {
            System.out.println("Job Priority : " + job.priority + ", Count : " + job.jobCount);
        }
    }

    static class Job implements Runnable {
        private int priority;
        private long jobCount;

        public Job(int priority) {
            this.priority = priority;
        }

        @Override
        public void run() {
            while (notStart) {
                Thread.yield();
            }
            while (notEnd) {
                Thread.yield();
                jobCount++;
            }
        }
    }
}

運行該示例,在筆者機器上對應的輸出以下

筆者使用的環境爲:Win10 + JDK11,從輸出能夠看到線程優先級起做用了


線程的狀態

Java 線程在運行的生命週期中可能處於下表所示的六種不一樣的狀態,在給定的一個時刻,線程只能處於其中的一個狀態

狀態名稱 說明
NEW 初始狀態,線程被構建,但還沒調用 start() 方法
RUNNABLE 運行狀態,Java 線程將操做系統中的就緒和運行兩種狀態籠統地稱做「運行中」
BLOCKED 阻塞狀態,表示線程阻塞於鎖
WAITING 等待狀態,表示線程進入等待狀態,進入該狀態表示當前線程須要等待其餘線程作出一些特定動做(通知或中斷)
TIME_WAITING 超時等待狀態,該狀態不一樣於 WAITING,它是能夠在指定的時間自行返回的
TERMINATED 終止狀態,表示當前線程已經執行完畢

線程在自身的生命週期中,並非固定地處於某一狀態,而是隨着代碼的執行在不一樣的狀態之間進行切換


Daemon 線程

Daemon 線程是一種支持型線程,主要被用做程序中後臺調度以及支持性工做。這意味着,當一個 Java 虛擬機中不存在 Daemon 線程的時候,Java 虛擬機將退出。能夠調用 Thread.setDaemon(true) 將線程設置爲 Daemon 線程

使用 Daemon 線程須要注意兩點:

  • Daemon 屬性須要在啓動線程以前設置,不能在啓動線程以後設置
  • 在構建 Daemon 線程時,不能依靠 finally 塊中的內容來確保執行或關閉清理資源的邏輯。由於在 Java 虛擬機退出時 Daemon 線程中的 finally 塊並不必定會執行

啓動和終止線程

1. 構造線程

在運行線程以前首先要構造一個線程對象,線程對象在構造的時候需提供線程需的屬性,如線程所屬的線程組、是不是 Daemon 線程等信息

2. 啓動線程

線程對象在初始化完成以後,調用 start() 方法便可啓動線程

3. 理解中斷

中斷能夠理解爲線程的一個標識位屬性,標識一個運行中的線程是否被其餘線程進行了中斷操做。中斷比如其餘線程對該線程打了個招呼,其餘線程能夠經過調用該線程的 interrupt() 方法對其進行中斷操做

線程經過檢查自身是否被中斷進行響應,線程經過 isInterrupted() 來進行判斷是否被中斷,也能夠調用靜態方法 Tread.interrupted() 對當前線程的中斷標識位進行復位。若是線程已經處於終結狀態,即時線程被中斷過,在調用該對象的 isInterrupted() 時依舊會返回 false

許多聲明拋出 InterruptedException 的方法在拋出異常以前,Java 虛擬機會先將該線程的中斷標識位清除,而後拋出 InterruptedException,此時調用 isInterrupted() 方法將會返回 false

在下面的例子中,首先建立兩個線程 SleepThread 和 BusyThread,前者不停地睡眠,後者一直運行,分別對兩個線程分別進行中斷操做,觀察中斷標識位

public class Interrupted {

    public static void main(String[] args) throws InterruptedException {
        // sleepThread 不停的嘗試睡眠
        Thread sleepThread = new Thread(new SleepRunner(), "SleepThread");
        sleepThread.setDaemon(true);
        // busyThread 不停的運行
        Thread busyThread = new Thread(new BusyRunner(), "BusyThread");
        busyThread.setDaemon(true);
        sleepThread.start();
        busyThread.start();
        // 休眠 5 秒,讓 sleepThread 和 busyThread 充分運行
        TimeUnit.SECONDS.sleep(5);
        sleepThread.interrupt();
        busyThread.interrupt();
        System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());
        System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());
        // 防止 sleepThread 和 busyThreaad 馬上退出
        SleepUtils.second(2);
    }

    static class SleepRunner implements Runnable {

        @Override
        public void run() {
            while (true) {
                SleepUtils.second(10);
            }
        }
    }

    static class BusyRunner implements Runnable {

        @Override
        public void run() {
            while (true) {

            }
        }
    }
}

輸出以下

從結果能夠看出,拋出 InterruptedException 的線程 SleepThread,其中斷標識位被清除了,而一直忙碌運行的線程 BusyThread 的中斷標識位沒有被清除

4. 安全地終止線程

前面提到的中斷操做是一種簡便的線程間交互方式,適合用來取消或中止任務。除了中斷之外,還能夠利用一個 boolean 變量來控制是否須要中止任務並終止線程

下面的示例中,建立了一個線程 CountThread,它不斷地進行變量累加,而主線程嘗試對其進行中斷操做和中止操做

public class Shutdown {

    public static void main(String[] args) throws InterruptedException {
        Runner one = new Runner();
        Thread countThread = new Thread(one, "CountThread");
        countThread.start();
        // 睡眠一秒,main 線程對 CountThread 進行中斷,使 CountThread 可以感知中斷而結束
        TimeUnit.SECONDS.sleep(1);
        countThread.interrupt();
        Runner two = new Runner();
        countThread = new Thread(two, "CountThread");
        countThread.start();
        // 睡眠一秒,main 線程對 Runner two 進行中斷,使 CountThread 可以感知 on 爲 false 而結束
        TimeUnit.SECONDS.sleep(1);
        two.cancel();
    }

    private static class Runner implements Runnable {

        private long i;
        private volatile boolean on = true;

        @Override
        public void run() {
            while (on && !Thread.currentThread().isInterrupted()) {
                i++;
            }
            System.out.println("Count i = " + i);
        }

        public void cancel() {
            on = false;
        }
    }
}

main 線程經過中斷操做和 cancel() 方法都可使 CountThread 得以終止。這種經過標識位或者中斷操做的方式可以使線程在終止時有機會去清理資源,而不是武斷地將線程中止,更加安全和優雅

相關文章
相關標籤/搜索