最近中文字幕高清中文字幕无,亚洲欧美高清一区二区三区,一本色道无码道dvd在线观看 ,一个人看的www免费高清中文字幕

全部開發(fā)者教程

Java 多線程

本小節(jié)我們將學(xué)習(xí) Java 多線程,通過本小節(jié)的學(xué)習(xí),你將了解到什么是線程,如何創(chuàng)建線程,創(chuàng)建線程有哪幾種方式,線程的狀態(tài)、生命周期等內(nèi)容。掌握多線程的代碼編寫,并理解線程生命周期等內(nèi)容是本小節(jié)學(xué)習(xí)的重點(diǎn)。

1. 什么是線程

要了解什么是線程,就要先了解進(jìn)程的概念。

進(jìn)程,是指計算機(jī)中已運(yùn)行的程序,它是一個動態(tài)執(zhí)行的過程。假設(shè)我們電腦上同時運(yùn)行了瀏覽器、QQ 以及代碼編輯器三個軟件,這三個軟件之所以同時運(yùn)行,就是進(jìn)程所起的作用。

線程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。大部分情況下,它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。也就是說一個進(jìn)程可以包含多個線程, 因此線程也被稱為輕量級進(jìn)程。

如果你還是對于進(jìn)程和線程的概念有所困惑,推薦一篇比較優(yōu)秀的文章,有助于幫助你理解進(jìn)程和線程的概念。

2. 創(chuàng)建線程

在 Java 中,創(chuàng)建線程有以下 3 種方式:

  1. 繼承 Thread 類,重寫 run() 方法,該方法代表線程要執(zhí)行的任務(wù);
  2. 實(shí)現(xiàn) Runnable 接口,實(shí)現(xiàn) run() 方法,該方法代表線程要執(zhí)行的任務(wù);
  3. 實(shí)現(xiàn) Callable 接口,實(shí)現(xiàn) call() 方法,call() 方法作為線程的執(zhí)行體,具有返回值,并且可以對異常進(jìn)行聲明和拋出。

下面我們分別來看下這 3 種方法的具體實(shí)現(xiàn)。

2.1 Thread 類

Thread 類是一個線程類,位于 java.lang 包下。

2.1.1 構(gòu)造方法

Thread 類的常用構(gòu)造方法如下:

  • Thread():創(chuàng)建一個線程對象;
  • Thread(String name):創(chuàng)建一個指定名稱的線程對象;
  • Thread(Runnable target):創(chuàng)建一個基于 Runnable 接口實(shí)現(xiàn)類的線程對象;
  • Thread(Runnable target, String name):創(chuàng)建一個基于 Runnable 接口實(shí)現(xiàn)類,并具有指定名稱的線程對象。

2.1.2 常用方法

void run():線程相關(guān)的代碼寫在該方法中,一般需要重寫;

void start():啟動當(dāng)前線程;

static void sleep(long m):使當(dāng)前線程休眠 m 毫秒;

void join():優(yōu)先執(zhí)行調(diào)用 join() 方法的線程。

Tips:run() 方法是一個非常重要的方法,它是用于編寫線程執(zhí)行體的方法,不同線程之間的一個最主要區(qū)別就是 run() 方法中的代碼是不同的。

可翻閱官方文檔以查看更多 API。

2.1.3 實(shí)例

通過繼承 Thread 類創(chuàng)建線程可分為以下 3 步:

  1. 定義 Thread 類的子類,并重寫該類的 run() 方法。run() 方法的方法體就代表了線程要完成的任務(wù);
  2. 創(chuàng)建 Thread 子類的實(shí)例,即創(chuàng)建線程對象;
  3. 調(diào)用線程對象的 start 方法來啟動該線程。

具體實(shí)例如下:

/**
 * @author colorful@TaleLin
 */
public class ThreadDemo1 extends Thread {

    /**
     * 重寫 Thread() 的方法
     */
    @Override
    public void run() {
        System.out.println("這里是線程體");
        // 當(dāng)前打印線程的名稱
        System.out.println(getName());
    }

    public static void main(String[] args) {
        // 實(shí)例化 ThreadDemo1 對象
        ThreadDemo1 threadDemo1 = new ThreadDemo1();
        // 調(diào)用 start() 方法,以啟動線程
        threadDemo1.start();
    }

}

運(yùn)行結(jié)果:

這里是線程體
Thread-0

小伙伴們可能會有疑問,上面這樣的代碼,和普通的類實(shí)例化以及方法調(diào)用有什么區(qū)別的,下面我們來看一個稍微復(fù)雜些的實(shí)例:

/**
 * @author colorful@TaleLin
 */
public class ThreadDemo2 {

    /**
     * 靜態(tài)內(nèi)部類
     */
    static class MyThread extends Thread {

        private int i = 3;

        MyThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            while (i > 0) {
                System.out.println(getName() + " i = " + i);
                i--;
            }
        }

    }

    public static void main(String[] args) {
        // 創(chuàng)建兩個線程對象
        MyThread thread1 = new MyThread("線程1");
        MyThread thread2 = new MyThread("線程2");
        // 啟動線程
        thread1.start();
        thread2.start();
    }

}

運(yùn)行結(jié)果:

線程2 i = 3
線程1 i = 3
線程1 i = 2
線程2 i = 2
線程1 i = 1
線程2 i = 1

代碼中我們是先啟動了線程 1,再啟動了線程 2 的,觀察運(yùn)行結(jié)果,線程并不是按照我們所預(yù)想的順序執(zhí)行的。這里就要劃重點(diǎn)了,不同線程,執(zhí)行順序是隨機(jī)的。如果你再執(zhí)行幾次代碼,可以觀察到每次的運(yùn)行結(jié)果都可能不同:

2.2 Runnable 接口

2.2.1 為什么需要 Runnable 接口

通過實(shí)現(xiàn) Runnable 接口的方案來創(chuàng)建線程,要優(yōu)于繼承 Thread 類的方案,主要有以下原因:

  1. Java 不支持多繼承,所有的類都只允許繼承一個父類,但可以實(shí)現(xiàn)多個接口。如果繼承了 Thread 類就無法繼承其它類,這不利于擴(kuò)展;
  2. 繼承 Thread 類通常只重寫 run() 方法,其他方法一般不會重寫。繼承整個 Thread 類成本過高,開銷過大。

2.2.2 實(shí)例

通過實(shí)現(xiàn) Runnable 接口創(chuàng)建線程的步驟如下:

  1. 定義 Runnable 接口的實(shí)現(xiàn)類,并實(shí)現(xiàn)該接口的 run() 方法。這個 run() 方法的方法體同樣是該線程的線程執(zhí)行體;
  2. 創(chuàng)建 Runnable 實(shí)現(xiàn)類的實(shí)例,并以此實(shí)例作為 Threadtarget 來創(chuàng)建 Thread 對象,該 Thread 對象才是真正的線程對象;
  3. 調(diào)用線程對象的 start 方法來啟動該線程。

具體實(shí)例如下:

/**
 * @author colorful@TaleLin
 */
public class RunnableDemo1 implements Runnable {

    private int i = 5;

    @Override
    public void run() {
        while (i > 0) {
            System.out.println(Thread.currentThread().getName() + " i = " + i);
            i--;
        }
    }

    public static void main(String[] args) {
        // 創(chuàng)建兩個實(shí)現(xiàn) Runnable 實(shí)現(xiàn)類的實(shí)例
        RunnableDemo1 runnableDemo1 = new RunnableDemo1();
        RunnableDemo1 runnableDemo2 = new RunnableDemo1();
        // 創(chuàng)建兩個線程對象
        Thread thread1 = new Thread(runnableDemo1, "線程1");
        Thread thread2 = new Thread(runnableDemo2, "線程2");
        // 啟動線程
        thread1.start();
        thread2.start();
    }

}

運(yùn)行結(jié)果:

線程1 i = 5
線程1 i = 4
線程1 i = 3
線程1 i = 2
線程2 i = 5
線程1 i = 1
線程2 i = 4
線程2 i = 3
線程2 i = 2
線程2 i = 1

2.3 Callable 接口

2.3.1 為什么需要 Callable 接口

繼承 Thread 類和實(shí)現(xiàn) Runnable 接口這兩種創(chuàng)建線程的方式都沒有返回值。所以,線程執(zhí)行完畢后,無法得到執(zhí)行結(jié)果。為了解決這個問題,Java 5 后,提供了 Callable 接口和 Future 接口,通過它們,可以在線程執(zhí)行結(jié)束后,返回執(zhí)行結(jié)果。

2.3.2 實(shí)例

通過實(shí)現(xiàn) Callable 接口創(chuàng)建線程步驟如下:

  1. 創(chuàng)建 Callable 接口的實(shí)現(xiàn)類,并實(shí)現(xiàn) call() 方法。這個 call() 方法將作為線程執(zhí)行體,并且有返回值;
  2. 創(chuàng)建 Callable 實(shí)現(xiàn)類的實(shí)例,使用 FutureTask 類來包裝 Callable 對象,這個 FutureTask 對象封裝了該 Callable 對象的 call() 方法的返回值;
  3. 使用 FutureTask 對象作為 Thread 對象的 target 創(chuàng)建并啟動新線程;
  4. 調(diào)用 FutureTask 對象的 get() 方法來獲得線程執(zhí)行結(jié)束后的返回值。

具體實(shí)例如下:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @author colorful@TaleLin
 */
public class CallableDemo1 {

    static class MyThread implements Callable<String> {

        @Override
        public String call() { // 方法返回值類型是一個泛型,在上面 Callable<String> 處定義
            return "我是線程中返回的字符串";
        }

    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 常見實(shí)現(xiàn)類的實(shí)例
        Callable<String> callable = new MyThread();
        // 使用 FutureTask 類來包裝 Callable 對象
        FutureTask<String> futureTask = new FutureTask<>(callable);
        // 創(chuàng)建 Thread 對象
        Thread thread = new Thread(futureTask);
        // 啟動線程
        thread.start();
        // 調(diào)用 FutureTask 對象的 get() 方法來獲得線程執(zhí)行結(jié)束后的返回值
        String s = futureTask.get();
        System.out.println(s);
    }

}

運(yùn)行結(jié)果:

我是線程中返回的字符串

3. 線程休眠

在前面介紹 Thread 類的常用方法時,我們介紹了 sleep() 靜態(tài)方法,該方法可以使當(dāng)前執(zhí)行的線程睡眠(暫時停止執(zhí)行)指定的毫秒數(shù)。

線程休眠的實(shí)例如下:

/**
 * @author colorful@TaleLin
 */
public class SleepDemo implements Runnable {

    @Override
    public void run() {
        for (int i = 1; i <= 5; i ++) {
            // 打印語句
            System.out.println(Thread.currentThread().getName() + ":執(zhí)行第" + i + "次");
            try {
                // 使當(dāng)前線程休眠
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    public static void main(String[] args) {
        // 實(shí)例化 Runnable 的實(shí)現(xiàn)類
        SleepDemo sleepDemo = new SleepDemo();
        // 實(shí)例化線程對象
        Thread thread = new Thread(sleepDemo);
        // 啟動線程
        thread.start();
    }

}

運(yùn)行結(jié)果:

Thread-0:執(zhí)行第1次
Thread-0:執(zhí)行第2次
Thread-0:執(zhí)行第3次
Thread-0:執(zhí)行第4次
Thread-0:執(zhí)行第5次

4. 線程的狀態(tài)和生命周期

java.lang.Thread.Starte 枚舉類中定義了 6 種不同的線程狀態(tài):

  1. NEW:新建狀態(tài),尚未啟動的線程處于此狀態(tài);
  2. RUNNABLE:可運(yùn)行狀態(tài),Java 虛擬機(jī)中執(zhí)行的線程處于此狀態(tài);
  3. BLOCK:阻塞狀態(tài),等待監(jiān)視器鎖定而被阻塞的線程處于此狀態(tài);
  4. WAITING:等待狀態(tài),無限期等待另一線程執(zhí)行特定操作的線程處于此狀態(tài);
  5. TIME_WAITING:定時等待狀態(tài),在指定等待時間內(nèi)等待另一線程執(zhí)行操作的線程處于此狀態(tài);
  6. TERMINATED:結(jié)束狀態(tài),已退出的線程處于此狀態(tài)。

值得注意的是,一個線程在給定的時間點(diǎn)只能處于一種狀態(tài)。這些狀態(tài)是不反映任何操作系統(tǒng)線程狀態(tài)的虛擬機(jī)狀態(tài)。

線程的生命周期,實(shí)際上就是上述 6 個線程狀態(tài)的轉(zhuǎn)換過程。下圖展示了一個完整的生命周期:

5. 小結(jié)

通過本小節(jié)的學(xué)習(xí),我們知道了線程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。線程也被稱為輕量級進(jìn)程。在 Java 中,可以以 3 種方式創(chuàng)建線程,分別是繼承 Thread 類、實(shí)現(xiàn) Runnable 接口以及實(shí)現(xiàn) Callable 接口。可以使用靜態(tài)方法 sleep() 讓線程休眠。線程狀態(tài)有 6 種,也有資料上說線程有 5 種,這部分內(nèi)容我們按照 Java 源碼中的定義 6 種來記憶即可。