Java多線程-線程的概念和創(chuàng)建

前言

聲明:該文章中所有測試都是在JDK1.8的環(huán)境下等太。

該文章是我在學(xué)習(xí)Java中的多線程這方面知識時架专,做的一些總結(jié)和記錄。

如果有不正確的地方請大家多多包涵并作出指點殖氏,謝謝蛉谜!

本文章有些內(nèi)容參考并采用以下作品內(nèi)容:

https://www.cnblogs.com/vipstone/p/14149065.html

https://www.bilibili.com/video/BV1dt4y1i7Gt?t=1056

一、基礎(chǔ)概念

我們知道CPU執(zhí)行代碼都是一條一條順序執(zhí)行的崇堵,所以本質(zhì)上單核CPU的電腦不會在同一個時間點執(zhí)行多個任務(wù)型诚。但是在現(xiàn)實中,我們在使用電腦的時候鸳劳,可以一邊聊微信狰贯,一邊聽歌。那這是怎么做到的呢赏廓?其實操作系統(tǒng)多任務(wù)就是讓CPU對多個任務(wù)輪流交替執(zhí)行涵紊。

舉個例子:在一個教室中同時坐著一年級,二年級捐晶,三年級三批學(xué)生县钥,老師花一分鐘教一年級卸察,花一分教二年級,花一分鐘教三年級驱负,這樣輪流下去,看上去就像同時在教三個年級患雇。

同樣的跃脊,我們使用電腦時,一邊聊微信苛吱,一邊聽歌也是這個原理酪术。CPU讓微信執(zhí)行0.001秒,讓音樂播放器執(zhí)行0.001秒翠储,在我們看來绘雁,CPU就是在同時執(zhí)行多個任務(wù)。

1.1 程序彰亥、進程和線程的概念

程序:被存儲在磁盤或其他的數(shù)據(jù)存儲設(shè)備中的可執(zhí)行文件咧七,也就是一堆靜態(tài)的代碼。

進程:運行在內(nèi)存中可執(zhí)行程序?qū)嵗?/p>

線程:線程是進程的一個實體任斋,是CPU調(diào)度和分派的基本單位继阻。

看著這些概念是不是很抽象耻涛,看的很不舒服,那么下面我來用實例解釋一下以上幾個概念瘟檩。

1.2 程序的運行實例

上面說到抹缕,我們使用電腦時,可以一邊聊微信墨辛,一邊聽歌卓研。那這些軟件運行的整個過程是怎樣的呢?

  • 如果我們要用微信聊天睹簇,大部分的人都是雙擊擊桌面上的"微信"快捷方式奏赘,而雙擊這個快捷方式打開的實際上是一個.exe文件,這個.exe文件就是一個程序太惠,請看下圖:
image-20210311152434762.png
  • 雙擊.exe文件后磨淌,加載可執(zhí)行程序到內(nèi)存中,方便CPU讀取凿渊,那這個可執(zhí)行文件程序?qū)嵗褪且粋€進程梁只。請看下圖:
image-20210311160217702.png
  • 而我們在使用微信的時候,微信會做很多事情埃脏,比如加載微信UI界面搪锣,顯示微信好友列表,與好友聊天彩掐,這些可以看成微信進程中一個個單獨的線程构舟。

我用一張圖來總結(jié)一下整個過程:

程序執(zhí)行流程.jpg

根據(jù)上面內(nèi)容對于線程概念的了解,是否有個疑問佩谷,線程是怎么創(chuàng)建出來的旁壮?帶著這個疑問我們就來學(xué)習(xí)一下java中的線程是怎么如何創(chuàng)建的。

二谐檀、線程的創(chuàng)建

2.1 Thread類的概念

java.lang.Thread類代表線程抡谐,任何線程都是Thread類(子類)的實例。

2.2 常用的方法

構(gòu)造方法 功能介紹
Thread() 使用無參的方式構(gòu)造對象
Thread(String name) 根據(jù)參數(shù)指定的名稱來構(gòu)造對象
Thread(Runnable target) 根據(jù)參數(shù)指定的引用來構(gòu)造對象桐猬,其中Runnable是個接口類型
Thread(Runnable target, String name) 根據(jù)參數(shù)指定引用和名稱來構(gòu)造對象
成員方法 功能介紹
run() 1.使用Runnable引用構(gòu)造線程對象麦撵,調(diào)用該方法時最終調(diào)用接口中的版本<br />2.沒有使用Runnable引用構(gòu)造線程對象,調(diào)用該方法時則啥也不做
start() 用于啟動線程溃肪,Java虛擬機會自動調(diào)用該線程的run方法

2.3 創(chuàng)建方式

2.3.1 自定義Thread類創(chuàng)建

自定義類繼承Thread類并根據(jù)自己的需求重寫run方法免胃,然后在主類中創(chuàng)建該類的對象調(diào)用start方法,這樣就啟動了一個線程惫撰。

示例代碼如下:

//創(chuàng)建一個自定義類SubThreadRun繼承Thread類羔沙,作為一個可以備用的線程類
public class SubThreadRun extends Thread {
    @Override
    public void run() {
        //打印1~20的整數(shù)值
        for (int i = 0; i < 20 ; i ++) {
            System.out.println("SubThreadRun線程中:" + i);
        }
    }
}

//在主方法中創(chuàng)建該線程并啟動
public class SubThreadRunTest {

    public static void main(String[] args) {

        //1.申明Thread類型的引用指向子類類型的對象
        Thread t1 = new SubThreadRun();
        //用于啟動線程,java虛擬機會自動調(diào)用該線程中的run方法
        t1.start();
    }
}


輸出結(jié)果:
    SubThreadRun線程中:0
    SubThreadRun線程中:2
    厨钻。扼雏。坚嗜。。诗充。苍蔬。
    SubThreadRun線程中:19

到這里大家會不會有以下一個疑問,看示例代碼:

public class SubThreadRunTest {

    public static void main(String[] args) {

        //1.申明Thread類型的引用指向子類類型的對象
        Thread t1 = new SubThreadRun();
        //調(diào)用run方法測試
        t1.run();
    }
}

輸出結(jié)果:
    SubThreadRun線程中:0
    SubThreadRun線程中:1
    蝴蜓。碟绑。。茎匠。格仲。。
    SubThreadRun線程中:19

我們不調(diào)用start方法诵冒,而是直接調(diào)用run方法抓狭,發(fā)現(xiàn)結(jié)果和調(diào)用start方法一樣,他們兩個方法的區(qū)別是啥呢造烁?

我們在主方法中也加入一個打印1-20的數(shù),然后分別用run方法和start方法進行測試午笛,實例代碼如下:

//使用run方法進行測試
public class SubThreadRunTest {

    public static void main(String[] args) {

        //1.申明Thread類型的引用指向子類類型的對象
        Thread t1 = new SubThreadRun();
        //2.調(diào)用run方法測試
        t1.run();

        //打印1~20的整數(shù)值
        for (int i = 0; i < 20 ; i ++) {
            System.out.println("-----mian-----方法中:" + i);
        }
    }
}

輸出結(jié)果:
    SubThreadRun線程中:0
    SubThreadRun線程中:1
    惭蟋。。药磺。告组。。癌佩。//都是SubThreadRun線程中
    SubThreadRun線程中:19
    -----mian-----方法中:0
    -----mian-----方法中:1
    木缝。。围辙。我碟。。姚建。//都是-----mian-----方法中
    -----mian-----方法中:19
     
    
//使用start方法進行測試
public class SubThreadRunTest {

    public static void main(String[] args) {

        //1.申明Thread類型的引用指向子類類型的對象
        Thread t1 = new SubThreadRun();
        //用于啟動線程矫俺,java虛擬機會自動調(diào)用該線程中的run方法
        t1.start();

        //打印1~20的整數(shù)值
        for (int i = 0; i < 20 ; i ++) {
            System.out.println("-----mian-----方法中:" + i);
        }
    }
}

輸出結(jié)果:
    SubThreadRun線程中:0
    -----mian-----方法中:0
    SubThreadRun線程中:1
    SubThreadRun線程中:2
    -----mian-----方法中:1
    。掸冤。厘托。。稿湿。铅匹。//SubThreadRun線程和mian方法相互穿插
    SubThreadRun線程中:19
    -----mian-----方法中:19

從上面的例子可知:

  • 調(diào)用run方法測試時,本質(zhì)上就是相當(dāng)于對普通成員方法的調(diào)用饺藤,因此執(zhí)行流程就是run方法的代碼執(zhí)行完畢后才能繼續(xù)向下執(zhí)行包斑。
  • 調(diào)用start方法測試時流礁,相當(dāng)于又啟動了一個線程,加上執(zhí)行main方法的線程舰始,一共有兩個線程崇棠,這兩個線程同時運行,所以輸出結(jié)果是交錯的丸卷。(現(xiàn)在回過頭來想想枕稀,現(xiàn)在是不是有點理解我可以用qq音樂一邊聽歌,一邊在打字評論了呢谜嫉。)

第一種創(chuàng)建線程的方式我們已經(jīng)學(xué)會了萎坷,那這種創(chuàng)建線程的方式有沒有什么缺陷呢?假如SubThreadRun類已經(jīng)繼承了一個父類沐兰,這個時候我們又要把該類作為自定義線程類哆档,如果還是用繼承Thread類的方法來實現(xiàn)的話就違背了Java不可多繼承的概念。所以第二種創(chuàng)建方式就可以避免這種問題住闯。

2.3.2 通過實現(xiàn)Runnable接口實現(xiàn)創(chuàng)建

自定義類實現(xiàn)Runnable接口并重寫run方法瓜浸,創(chuàng)建該類的對象作為實參來構(gòu)造Thread類型的對象,然后使用Thread類型的對象調(diào)用start方法比原。

示例代碼如下:

//創(chuàng)建一個自定義類SubRunnableRun實現(xiàn)Runnable接口
public class SubRunnableRun implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 20 ; i ++) {
            System.out.println("SubRunnableRun線程中:" + i);
        }
    }
}

//在主方法中創(chuàng)建該線程并啟動
public class SunRunnableRunTest {

    public static void main(String[] args) {

        //1.創(chuàng)建自定義類型的對象
        SubRunnableRun srr = new SubRunnableRun();
        //2.使用該對象作為實參構(gòu)造Thread類型的對象
        Thread t1 = new Thread(srr);
        //3.使用Thread類型的對象調(diào)用start方法
        t1.start();

        //打印1~20之間的所有整數(shù)
        for (int i = 0; i < 20 ; i ++) {
            System.out.println("-----mian-----方法中:" + i);
        }
    }
}

輸出結(jié)果:
    SubRunnableRun線程中:0
    -----mian-----方法中:0
    SubRunnableRun線程中:1
    SubRunnableRun線程中:2
    -----mian-----方法中:1
    插佛。。量窘。雇寇。。蚌铜。//SubRunnableRun線程和mian方法相互穿插
    SubRunnableRun線程中:19
    -----mian-----方法中:19

到這里大家會不會有一個疑問呢锨侯?

我在SunRunnableRunTest類的main方法中也實例化了Thread類,為什么該線程調(diào)用的是實現(xiàn)了Runnable接口的SubRunnableRun類中的run方法冬殃,而不是Thread類中的run方法囚痴。

為了解決該疑問,我們就進入Thread類去看一下源碼造壮,源碼調(diào)用過程如下:

  1. 從上面的SunRunnableRunTest類中代碼可知渡讼,我們創(chuàng)建線程調(diào)用的是Thread的有參構(gòu)造方法,參數(shù)是Runnable類型的耳璧。

    image-20210319154539691.png
  2. 進入到Thread類中找到該有參構(gòu)造方法成箫,看到該構(gòu)造方法調(diào)用init方法,并且把target參數(shù)繼續(xù)當(dāng)參數(shù)傳遞過去旨枯。

image-20210319154832335.png
  1. 轉(zhuǎn)到對應(yīng)的init方法后蹬昌,發(fā)現(xiàn)該init方法繼續(xù)調(diào)用另一個重載的init方法,并且把target參數(shù)繼續(xù)當(dāng)參數(shù)傳遞過去攀隔。

    image-20210319155311497.png
  2. 繼續(xù)進入到重載的init方法中皂贩,我們發(fā)現(xiàn)栖榨,該方法中把參數(shù)中target賦值給成員變量target。

    image-20210319155901774.png
  3. 然后找到Thread類中的run方法明刷,發(fā)現(xiàn)只要Thread的成員變量target存在婴栽,就調(diào)用target中的run方法。

    image-20210319160341523.png

通過查看源碼辈末,我們可以知道為什么我們創(chuàng)建的Thread類調(diào)用的是Runnable類中的run方法愚争。

2.3.3 匿名內(nèi)部類的方式實現(xiàn)創(chuàng)建

上面兩種創(chuàng)建線程的方式都需要單獨創(chuàng)建一個類來繼承Thread類或者實現(xiàn)Runnable接口,并重寫run方法挤聘。而匿名內(nèi)部類可以不創(chuàng)建單獨的類而實現(xiàn)自定義線程的創(chuàng)建轰枝。

示例代碼如下:

public class ThreadNoNameTest {

    public static void main(String[] args) {

        //匿名內(nèi)部類的語法格式:父類/接口類型 引用變量 = new 父類/接口類型 {方法的重寫};
        //1.使用繼承加匿名內(nèi)部類的方式創(chuàng)建并啟動線程
        Thread t1 = new Thread() {
            @Override
            public void run() {
                System.out.println("繼承Thread類方式創(chuàng)建線程...");
            }
        };
        t1.start();
       
        //2.使用接口加匿名內(nèi)部類的方式創(chuàng)建并啟動線程
        Runnable ra = new Runnable() {
            @Override
            public void run() {
                System.out.println("實現(xiàn)Runnable接口方式實現(xiàn)線程...");
            }
        };
        Thread t2 = new Thread(ra);
        t2.start();
    }
}

這兩個利用匿名內(nèi)部類創(chuàng)建線程的方式還能繼續(xù)簡化代碼,尤其是使用Runnable接口創(chuàng)建線程的方式组去,可以使用Lambda表達式進行簡化鞍陨。

示例代碼如下:

public class ThreadNoNameTest {

    public static void main(String[] args) {

        //1.使用繼承加匿名內(nèi)部類簡化后的方式創(chuàng)建并啟動線程
        new Thread() {
            @Override
            public void run() {
                System.out.println("簡化后繼承Thread類方式創(chuàng)建線程...");
            }
        }.start();

        //2.使用接口加匿名內(nèi)部類簡化后的方式創(chuàng)建并啟動線程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("簡化后實現(xiàn)Runnable接口方式實現(xiàn)線程...");
            }
        }).start();
        
        //2-1.對于接口加匿名內(nèi)部創(chuàng)建線程,可以繼續(xù)使用lambda表達式進行簡化从隆。
        //Java8開始支持lambda表達式:(形參列表) -> {方法體;}
        Runnable ra = () -> System.out.println("lambda表達式簡化實現(xiàn)Runnable接口方式實現(xiàn)線程...");
        new Thread(ra).start();
        
        //繼續(xù)簡化
        new Thread(() -> System.out.println("lambda表達式簡化實現(xiàn)Runnable接口方式實現(xiàn)線程...")).start();
    }
}

2.3.4 通過實現(xiàn)Callable接口創(chuàng)建

通過上面幾個例子诚撵,我們了解了兩種創(chuàng)建線程的方式,但是這兩種方式創(chuàng)建線程存在一個問題键闺,就是run方法是沒有返回值的砾脑,所以如果我們希望在線程結(jié)束之后給出一個結(jié)果,那就需要用到實現(xiàn)Callable接口創(chuàng)建線程艾杏。

(1)Callable接口

從Java5開始新增創(chuàng)建線程的第三中方式為實現(xiàn)java.util.concurrent.Callable接口。

常用方法如下:

成員方法 功能介紹
V call() 計算結(jié)果盅藻,如果無法計算結(jié)果购桑,則拋出一個異常

我們知道啟動線程只有創(chuàng)建一個Thread類并調(diào)用start方法,如果想讓線程啟動時調(diào)用到Callable接口中的call方法氏淑,就得用到FutureTask類勃蜘。

(2)FutureTask類

java.util.concurrent.FutureTask類實現(xiàn)了RunnableFuture接口,RunnableFuture接口是Runnable和Future的綜合體假残,作為一個Future缭贡,F(xiàn)utureTask可以執(zhí)行異步計算,可以查看異步程序是否執(zhí)行完畢辉懒,并且可以開始和取消程序阳惹,并取得程序最終的執(zhí)行結(jié)果,也可以用于獲取調(diào)用方法后的返回結(jié)果眶俩。

常用方法如下:

構(gòu)造方法 功能介紹
FutureTask(Callable<v> callable) 創(chuàng)建一個FutureTask莹汤,它將在運行時執(zhí)行給定的Callable
成員方法 功能介紹
V get() 獲取call方法計算的結(jié)果

從上面的概念可以了解到FutureTask類的一個構(gòu)造方法是以Callable為參數(shù)的,然后FutureTask類是Runnable的子類颠印,所以FutureTask類可以作為Thread類的參數(shù)纲岭。這樣的話我們就可以創(chuàng)建一個線程并調(diào)用Callable接口中的call方法抹竹。

實例代碼如下:

public class ThreadCallableTest implements Callable {
    @Override
    public Object call() throws Exception {
        //計算1 ~ 10000之間的累加和并打印返回
        int sum = 0;
        for (int i = 0; i <= 10000; i ++) {
            sum += i;
        }
        System.out.println("call方法中的結(jié)果:" + sum);
        return sum;
    }

    public static void main(String[] args) {

        ThreadCallableTest tct = new ThreadCallableTest();
        FutureTask ft = new FutureTask(tct);
        Thread t1 = new Thread(ft);
        t1.start();
        Object ob = null;
        try {
            ob = ft.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("main方法中的結(jié)果:" + ob);
    }
}

輸出結(jié)果:
    call方法中的結(jié)果:50005000
    main方法中的結(jié)果:50005000

2.3.5 線程池的創(chuàng)建

線程池的由來

在講線程池之前,先來講一個故事止潮,一個老板開個飯店窃判,但這個飯店很奇怪,每來一個顧客喇闸,老板就去招一個新的大廚來做菜袄琳,等這個顧客走后,老板直接把這個大廚辭了仅偎。如果是按這種經(jīng)營方式的話跨蟹,老板每天就忙著招大廚,啥事都干不了橘沥。

對于上面講的這個故事窗轩,我們現(xiàn)實生活中的飯店老板可沒有這么蠢,他們都是在開店前就直接招了好幾個大廚候在廚房座咆,等有顧客來了痢艺,直接做菜上菜,顧客走后介陶,廚師留在后廚待命堤舒,這樣就把老板解放了。

現(xiàn)在我們來講一下線程池的由來:比如說服務(wù)器編程中哺呜,如果為每一個客戶都分配一個新的工作線程舌缤,并且當(dāng)工作線程與客戶通信結(jié)束時,這個線程被銷毀某残,這就需要頻繁的創(chuàng)建和銷毀工作線程国撵。如果訪問服務(wù)器的客戶端過多,那么會嚴(yán)重影響服務(wù)器的性能玻墅。

那么我們該如何解放服務(wù)器呢介牙?對了,就像上面講的飯店老板一樣澳厢,打造一個后廚环础,讓廚師候著。相對于服務(wù)器來說剩拢,就創(chuàng)建一個線程池线得,讓線程候著,等待客戶端的連接徐伐,等客戶端結(jié)束通信后框都,服務(wù)器不關(guān)閉該線程,而是返回到線程中待命。這樣就解放了服務(wù)器魏保。

線程池的概念

首先創(chuàng)建一些線程熬尺,他們的集合稱為線程池,當(dāng)服務(wù)器接收到一個客戶請求后谓罗,就從線程池中取出一個空余的線程為之服務(wù)粱哼,服務(wù)完后不關(guān)閉線程,而是將線程放回到線程池中檩咱。

相關(guān)類和方法

  • 線程池的創(chuàng)建方法總共有 7 種揭措,但總體來說可分為 2 類:
image-20210418203505283.png
  • Executors是一個工具類和線程池的工廠類,用于創(chuàng)建并返回不同類型的線程池刻蚯,常用的方法如下:

    返回值 方法 功能介紹
    static ExecutorService newFixedThreadPool(int nThreads) 創(chuàng)建一個固定大小的線程池绊含,如果任務(wù)數(shù)超出線程數(shù),則超出的部分會在隊列中等待
    static ExecutorService newCachedThreadPool() 創(chuàng)建一個可已根據(jù)需要創(chuàng)建新線程的線程池炊汹,如果同一時間的任務(wù)數(shù)大于線程數(shù)躬充,則可以根據(jù)任務(wù)數(shù)創(chuàng)建新線程,如果任務(wù)執(zhí)行完成讨便,則緩存一段時間后線程被回收充甚。
    static ExecutorService newSingleThreadExecutor() 創(chuàng)建單個線程數(shù)的線程池,可以保證先進先出的執(zhí)行順序
    static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 創(chuàng)建一個可以執(zhí)行延遲任務(wù)的線程池
    static ScheduledExecutorService newSingleThreadScheduledExecutor() 創(chuàng)建一個單線程的可以執(zhí)行延遲任務(wù)的線程池
    static ExecutorService newWorkStealingPool() 創(chuàng)建一個搶占式執(zhí)行的線程池(任務(wù)執(zhí)行順序不確定)【JDK 1.8 添加】
  • ThreadPoolExecutor通過構(gòu)造方法創(chuàng)建線程池霸褒,最多可以設(shè)置7個參數(shù)伴找,創(chuàng)建線程池的構(gòu)造方法如下:

    構(gòu)造方法 功能介紹
    ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 通過最原始的方法創(chuàng)建線程池
  • 通過上面兩類方法創(chuàng)建完線程池后都可以用ExecutorService接口進行接收,它是真正的線程池接口废菱,主要實現(xiàn)類是ThreadPoolExecutor技矮,常用方法如下:

    方法聲明 功能介紹
    void execute(Runnable command) 執(zhí)行任務(wù)和命令,通常用于執(zhí)行Runnable
    <T> Future<T> submit(Callable<T> task) 執(zhí)行任務(wù)和命令殊轴,通常用于執(zhí)行Callable
    void shutdown() 啟動有序關(guān)閉

代碼實例

  1. 使用newFixedThreadPool方法創(chuàng)建線程池

    public class FixedThreadPool {
        public static void main(String[] args) {
            
            // 創(chuàng)建含有兩個線程的線程池
            ExecutorService threadPool = Executors.newFixedThreadPool(2);
            // 創(chuàng)建任務(wù)
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println("線程:" + Thread.currentThread().getName() + "執(zhí)行了任務(wù)穆役!");
                }
            };
            // 線程池執(zhí)行任務(wù)
            threadPool.execute(runnable);
            threadPool.execute(runnable);
            threadPool.execute(runnable);
            threadPool.execute(runnable);
        }
    }
    
    輸出結(jié)果:
        線程:pool-1-thread-2執(zhí)行了任務(wù)!
        線程:pool-1-thread-1執(zhí)行了任務(wù)梳凛!
        線程:pool-1-thread-2執(zhí)行了任務(wù)!
        線程:pool-1-thread-1執(zhí)行了任務(wù)梳杏!
    

    從結(jié)果上可以看出韧拒,這四個任務(wù)分別被線程池中的固定的兩個線程所執(zhí)行,線程池也不會創(chuàng)建新的線程來執(zhí)行任務(wù)十性。

  2. 使用newCachedThreadPool方法創(chuàng)建線程池

    public class cachedThreadPool {
        public static void main(String[] args) {
    
            //1.創(chuàng)建線程池
            ExecutorService executorService = Executors.newCachedThreadPool();
            //2.設(shè)置任務(wù)
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println("線程:" + Thread.currentThread().getName() + "執(zhí)行了任務(wù)叛溢!");
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                    }
                }
            };
            //3.執(zhí)行任務(wù)
            for (int i = 0; i < 100; i ++) {
                executorService.execute(runnable);
            }
        }
    }
    
    輸出結(jié)果:
        線程:pool-1-thread-1執(zhí)行了任務(wù)!
        線程:pool-1-thread-4執(zhí)行了任務(wù)劲适!
        線程:pool-1-thread-3執(zhí)行了任務(wù)楷掉!
        線程:pool-1-thread-2執(zhí)行了任務(wù)!
        線程:pool-1-thread-5執(zhí)行了任務(wù)霞势!
        線程:pool-1-thread-7執(zhí)行了任務(wù)烹植!
        線程:pool-1-thread-6執(zhí)行了任務(wù)斑鸦!
        線程:pool-1-thread-8執(zhí)行了任務(wù)!
        線程:pool-1-thread-9執(zhí)行了任務(wù)草雕!
        線程:pool-1-thread-10執(zhí)行了任務(wù)巷屿!
    

    從結(jié)果上可以看出,線程池根據(jù)任務(wù)的數(shù)量來創(chuàng)建對應(yīng)的線程數(shù)量墩虹。

  3. 使用newSingleThreadExecutor的方法創(chuàng)建線程池

    public class singleThreadExecutor {
    
        public static void main(String[] args) {
    
            //創(chuàng)建線程池
            ExecutorService executorService = Executors.newSingleThreadExecutor();
            //執(zhí)行任務(wù)
            for (int i = 0; i < 10; i ++) {
                final int task = i + 1;
                executorService.execute(()->{
                    System.out.println("線程:" + Thread.currentThread().getName() + "執(zhí)行了第" + task +"任務(wù)嘱巾!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        }
    }
    
    輸出結(jié)果:
        線程:pool-1-thread-1執(zhí)行了第1任務(wù)!
     線程:pool-1-thread-1執(zhí)行了第2任務(wù)诫钓!
     線程:pool-1-thread-1執(zhí)行了第3任務(wù)旬昭!
     線程:pool-1-thread-1執(zhí)行了第4任務(wù)!
     線程:pool-1-thread-1執(zhí)行了第5任務(wù)菌湃!
     線程:pool-1-thread-1執(zhí)行了第6任務(wù)问拘!
     線程:pool-1-thread-1執(zhí)行了第7任務(wù)!
     線程:pool-1-thread-1執(zhí)行了第8任務(wù)慢味!
     線程:pool-1-thread-1執(zhí)行了第9任務(wù)场梆!
     線程:pool-1-thread-1執(zhí)行了第10任務(wù)!
    

    從結(jié)果可以看出纯路,該方法創(chuàng)建的線程可以保證任務(wù)執(zhí)行的順序或油。

  4. 使用newScheduledThreadPool的方法創(chuàng)建線程池

    public class ScheduledThreadPool {
    
        public static void main(String[] args) {
    
            //創(chuàng)建包含2個線程的線程池
            ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
            //記錄創(chuàng)建任務(wù)時的當(dāng)前時間
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date startTime = new Date();
            String start = formatter.format(startTime);
            System.out.println("創(chuàng)建任務(wù)時的時間:" + start);
            //創(chuàng)建任務(wù)
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    Date endTime = new Date();
                    String end = formatter.format(endTime);
                    System.out.println("線程:" + Thread.currentThread().getName() + "任務(wù)執(zhí)行的時間為:" + end);
                }
            };
            //執(zhí)行任務(wù)(參數(shù):runnable-要執(zhí)行的任務(wù),2-從現(xiàn)在開始延遲執(zhí)行的時間驰唬,TimeUnit.SECONDS-延遲參數(shù)的時間單位)
            for(int i = 0; i < 2; i ++) {
                scheduledExecutorService.schedule(runnable,2, TimeUnit.SECONDS);
            }
        }
    }
    
    輸出結(jié)果:
        創(chuàng)建任務(wù)的時間:2021-04-19 19:26:18
     線程:pool-1-thread-1任務(wù)執(zhí)行的時間為:2021-04-19 19:26:20
     線程:pool-1-thread-2任務(wù)執(zhí)行的時間為:2021-04-19 19:26:20
    

    從結(jié)果可以看出顶岸,該方法創(chuàng)建的線程池可以分配已有的線程執(zhí)行一些需要延遲的任務(wù)。

  5. 使用newSingleThreadScheduledExecutor方法創(chuàng)建線程池

    public class SingleThreadScheduledExecutor {
    
        public static void main(String[] args) {
    
            //創(chuàng)建線程池
            ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
            //創(chuàng)建任務(wù)
            Date startTime = new Date();
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String start = formatter.format(startTime);
            System.out.println("創(chuàng)建任務(wù)的時間:" + start);
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    Date endTime = new Date();
                    String end = formatter.format(endTime);
                    System.out.println("線程:" + Thread.currentThread().getName() + "任務(wù)執(zhí)行的時間為:" + end);
                }
            };
            //執(zhí)行任務(wù)
            for(int i = 0; i < 2; i ++) {
                scheduledExecutorService.schedule(runnable,2, TimeUnit.SECONDS);
            }
        }
    }
    
    輸出結(jié)果:
        創(chuàng)建任務(wù)的時間:2021-04-19 19:51:58
     線程:pool-1-thread-1任務(wù)執(zhí)行的時間為:2021-04-19 19:52:00
     線程:pool-1-thread-1任務(wù)執(zhí)行的時間為:2021-04-19 19:52:00
    

    從結(jié)果可以看出叫编,該方法創(chuàng)建的線程池只有一個線程辖佣,該線程去執(zhí)行一些需要延遲的任務(wù)。

  6. 使用newWorkStealingPool方法創(chuàng)建線程池

    public class newWorkStealingPool {
    
        public static void main(String[] args) {
    
            //創(chuàng)建線程池
            ExecutorService executorService = Executors.newWorkStealingPool();
            //執(zhí)行任務(wù)
            for (int i = 0; i < 4; i ++) {
                final int task = i + 1;
                executorService.execute(()->{
                    System.out.println("線程:" + Thread.currentThread().getName() + "執(zhí)行了第" + task +"任務(wù)搓逾!");
                });
            }
            //確保任務(wù)被執(zhí)行
            while (!executorService.isTerminated()) {
            }
        }
    }
    
    輸出結(jié)果:
        線程:ForkJoinPool-1-worker-9執(zhí)行了第1任務(wù)卷谈!
     線程:ForkJoinPool-1-worker-4執(zhí)行了第4任務(wù)!
     線程:ForkJoinPool-1-worker-11執(zhí)行了第3任務(wù)霞篡!
     線程:ForkJoinPool-1-worker-2執(zhí)行了第2任務(wù)世蔗!
    

    從結(jié)果可以看出,該方法會創(chuàng)建一個含有足夠多線程的線程池朗兵,來維持相應(yīng)的并行級別污淋,任務(wù)會被搶占式執(zhí)行。(任務(wù)執(zhí)行順序不確定)

  7. 使用ThreadPoolExecutor創(chuàng)建線程池

    在編寫示例代碼之前我先來講一個生活的例子(去銀行辦理業(yè)務(wù)):

    描述業(yè)務(wù)場景:銀行一共有4個窗口余掖,今天只開放兩個寸爆,然后等候區(qū)一共3個位置。如下圖所示:

    銀行實例1.jpg
    • 如果銀行同時辦理業(yè)務(wù)的人小于等于5個人,那么正好赁豆,2個人先辦理仅醇,其他的人在等候區(qū)等待。如下圖所示:

      銀行實例2.jpg
    • 如果銀行同時辦理業(yè)務(wù)的人等于6個人時歌憨,銀行會開放三號窗口來辦理業(yè)務(wù)着憨。如下圖所示:

    銀行實例3.jpg
    • 如果銀行同時辦理業(yè)務(wù)的人等于7個人時,銀行會開放四號窗口來辦理業(yè)務(wù)务嫡。如下圖所示:
    銀行實例4.jpg
    • 如果銀行同時辦理業(yè)務(wù)的人大于7個人時甲抖,則銀行大廳經(jīng)理就會告訴后面的人,該網(wǎng)點業(yè)務(wù)已滿心铃,請去其他網(wǎng)點辦理准谚。

      銀行實例5.jpg

    現(xiàn)在我們再來看一下我們的ThreadPoolExecutor構(gòu)造方法,該構(gòu)造方法最多可以設(shè)置7個參數(shù):

    ThreadPoolExecutor(int corePoolSize, 
                    int maximumPoolSize, 
                       long keepAliveTime, 
                       TimeUnit unit, 
                       BlockingQueue<Runnable> workQueue, 
                       ThreadFactory threadFactory, 
                       RejectedExecutionHandler handler)
    

    參數(shù)介紹:

    1. corePoolSize:核心線程數(shù)去扣,在線程池中一直存在的線程(對應(yīng)銀行辦理業(yè)務(wù)模型:一開始就開放的窗口)
    2. maximumPoolSize:最大線程數(shù)柱衔,線程池中能創(chuàng)建最多的線程數(shù),除了核心線程數(shù)以外的幾個線程會在線程池的任務(wù)隊列滿了之后創(chuàng)建(對應(yīng)銀行辦理業(yè)務(wù)模型:所有窗口)
    3. keepAliveTime:最大線程數(shù)的存活時間愉棱,當(dāng)長時間沒有任務(wù)時唆铐,線程池會銷毀一部分線程,保留核心線程
    4. unit:時間單位奔滑,是第三個參數(shù)的單位艾岂,這兩個參數(shù)組合成最大線程數(shù)的存活時間
      • TimeUnit.DAYS:天
      • TimeUnit.HOURS:小時
      • TimeUnit.MINUTES:分
      • TimeUnit.SECONDS:秒
      • TimeUnit.MILLISECONDS:毫秒
      • TimeUnit.MICROSECONDS:微秒
      • TimeUnit.NANOSECONDS:納秒
    5. workQueue:等待隊列,用于保存在線程池等待的任務(wù)(對應(yīng)銀行辦理業(yè)務(wù)模型:等待區(qū))
      • ArrayBlockingQueue:一個由數(shù)組支持的有界阻塞隊列朋其。
      • LinkedBlockingQueue:一個由鏈表組成的有界阻塞隊列王浴。
      • SynchronousQueue:該阻塞隊列不儲存任務(wù),直接提交給線程梅猿,這樣就會形成對于提交的任務(wù)氓辣,如果有空閑線程,則使用空閑線程來處理袱蚓,否則新建一個線程來處理任務(wù)钞啸。
      • PriorityBlockingQueue:一個帶優(yōu)先級的無界阻塞隊列,每次出隊都返回優(yōu)先級最高或者最低的元素
      • DelayQueue:一個使用優(yōu)先級隊列實現(xiàn)支持延時獲取元素的無界阻塞隊列喇潘,只有在延遲期滿時才能從中提取元素体斩,現(xiàn)實中的使用: 淘寶訂單業(yè)務(wù):下單之后如果三十分鐘之內(nèi)沒有付款就自動取消訂單。
      • LinkedTransferQueue:一個由鏈表結(jié)構(gòu)組成的無界阻塞隊列响蓉。
      • LinkedBlockingDeque:一個由鏈表結(jié)構(gòu)組成的雙向阻塞隊列。
    6. threadFactory:線程工廠哨毁,用于創(chuàng)建線程枫甲。
    7. handler:拒絕策略,任務(wù)超出線程池可接受范圍時,拒絕處理任務(wù)時的策略想幻。
      • ThreadPoolExecutor.AbortPolicy:當(dāng)任務(wù)添加到線程池中被拒絕時粱栖,它將拋出 RejectedExecutionException 異常(默認使用該策略
      • ThreadPoolExecutor.CallerRunsPolicy:當(dāng)任務(wù)添加到線程池中被拒絕時,會調(diào)用當(dāng)前線程池的所在的線程去執(zhí)行被拒絕的任務(wù)
      • ThreadPoolExecutor.DiscardOldestPolicy:當(dāng)任務(wù)添加到線程池中被拒絕時脏毯,會拋棄任務(wù)隊列中最舊的任務(wù)也就是最先加入隊列的闹究,再把這個新任務(wù)添加進去
      • ThreadPoolExecutor.DiscardPolicy:如果該任務(wù)被拒絕,這直接忽略或者拋棄該任務(wù)

    當(dāng)任務(wù)數(shù)小于等于核心線程數(shù)+等待隊列數(shù)量的總和時

    public class ThreadPoolExecutorTest {
    
        public static void main(String[] args) {
    
            // 創(chuàng)建線程池
            ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
            //創(chuàng)建任務(wù)
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "==>執(zhí)行任務(wù)");
                }
            };
            // 執(zhí)行任務(wù)
            for (int i = 0; i < 5; i++) {
                threadPool.execute(runnable);
            }
            //關(guān)閉線程池
            threadPool.shutdown();
        }
    }
    
    
    輸出結(jié)果:
     pool-1-thread-2==>執(zhí)行任務(wù)
     pool-1-thread-1==>執(zhí)行任務(wù)
     pool-1-thread-2==>執(zhí)行任務(wù)
     pool-1-thread-1==>執(zhí)行任務(wù)
     pool-1-thread-2==>執(zhí)行任務(wù)
    

    從結(jié)果中可以看出食店,只有兩個核心線程在執(zhí)行任務(wù)渣淤。

    當(dāng)任務(wù)數(shù)大于核心線程數(shù)+等待隊列數(shù)量的總和,但是小于等于最大線程數(shù)時

    public class ThreadPoolExecutorTest {
    
        public static void main(String[] args) {
    
            // 創(chuàng)建線程池
            ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
            //創(chuàng)建任務(wù)
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "==>執(zhí)行任務(wù)");
                }
            };
            // 執(zhí)行任務(wù)
            for (int i = 0; i < 7; i++) {
                threadPool.execute(runnable);
            }
            //關(guān)閉線程池
            threadPool.shutdown();
        }
    }
    
    輸出結(jié)果:
     pool-1-thread-1==>執(zhí)行任務(wù)
     pool-1-thread-4==>執(zhí)行任務(wù)
     pool-1-thread-2==>執(zhí)行任務(wù)
     pool-1-thread-2==>執(zhí)行任務(wù)
     pool-1-thread-3==>執(zhí)行任務(wù)
     pool-1-thread-4==>執(zhí)行任務(wù)
     pool-1-thread-1==>執(zhí)行任務(wù)
    

    從結(jié)果中可以看出吉嫩,啟動了最大線程來執(zhí)行任務(wù)价认。

    當(dāng)任務(wù)數(shù)大于最大線程數(shù)時

    public class ThreadPoolExecutorTest {
    
        public static void main(String[] args) {
    
            // 創(chuàng)建線程池
            ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
            //創(chuàng)建任務(wù)
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "==>執(zhí)行任務(wù)");
                }
            };
            // 執(zhí)行任務(wù)
            for (int i = 0; i < 8; i++) {
                threadPool.execute(runnable);
            }
            //關(guān)閉線程池
            threadPool.shutdown();
        }
    }
    
    輸出結(jié)果:
    Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.zck.task18.ThreadPool.ThreadPoolExecutorTest$1@7f31245a rejected from java.util.concurrent.ThreadPoolExecutor@6d6f6e28[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 7]
     at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
     at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
     at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
     at com.zck.task18.ThreadPool.ThreadPoolExecutorTest.main(ThreadPoolExecutorTest.java:25)
        pool-1-thread-1==>執(zhí)行任務(wù)
     pool-1-thread-4==>執(zhí)行任務(wù)
     pool-1-thread-4==>執(zhí)行任務(wù)
     pool-1-thread-4==>執(zhí)行任務(wù)
     pool-1-thread-2==>執(zhí)行任務(wù)
     pool-1-thread-3==>執(zhí)行任務(wù)
     pool-1-thread-1==>執(zhí)行任務(wù)
    
    

    從結(jié)果中可以看出,任務(wù)大于最大線程數(shù)自娩,使用拒絕策略直接拋出異常用踩。

三、總結(jié)

本文介紹了三種線程的創(chuàng)建方式:

  • 自定義類繼承Thread類并重寫run方法創(chuàng)建
  • 自定義類實現(xiàn)Runnable接口并重寫run方法創(chuàng)建
  • 實現(xiàn)Callable接口創(chuàng)建

介紹了七種線程池的創(chuàng)建方式:

  • 使用newFixedThreadPool方法創(chuàng)建線程池
  • 使用newCachedThreadPool方法創(chuàng)建線程池
  • 使用newSingleThreadExecutor的方法創(chuàng)建線程池
  • 使用newScheduledThreadPool的方法創(chuàng)建線程池
  • 使用newSingleThreadScheduledExecutor方法創(chuàng)建線程池
  • 使用newWorkStealingPool方法創(chuàng)建線程池
  • 使用ThreadPoolExecutor創(chuàng)建線程池
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末忙迁,一起剝皮案震驚了整個濱河市脐彩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌姊扔,老刑警劉巖惠奸,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異旱眯,居然都是意外死亡晨川,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門删豺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來共虑,“玉大人,你說我怎么就攤上這事呀页÷璋瑁” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵蓬蝶,是天一觀的道長唯沮。 經(jīng)常有香客問我鲤竹,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任巍虫,我火速辦了婚禮,結(jié)果婚禮上藤违,老公的妹妹穿的比我還像新娘粥脚。我一直安慰自己谍咆,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布私股。 她就那樣靜靜地躺著摹察,像睡著了一般。 火紅的嫁衣襯著肌膚如雪倡鲸。 梳的紋絲不亂的頭發(fā)上供嚎,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音峭状,去河邊找鬼克滴。 笑死,一個胖子當(dāng)著我的面吹牛宁炫,可吹牛的內(nèi)容都是我干的偿曙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼羔巢,長吁一口氣:“原來是場噩夢啊……” “哼望忆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起竿秆,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤启摄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后幽钢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體歉备,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年匪燕,在試婚紗的時候發(fā)現(xiàn)自己被綠了蕾羊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡帽驯,死狀恐怖龟再,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情尼变,我是刑警寧澤利凑,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站嫌术,受9級特大地震影響哀澈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜度气,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一割按、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧磷籍,春花似錦适荣、人聲如沸丙躏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至栅盲,卻和暖如春汪诉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谈秫。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工扒寄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拟烫。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓该编,卻偏偏與公主長得像,于是被迫代替她去往敵國和親硕淑。 傳聞我的和親對象是個殘疾皇子课竣,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容