別翻了,這篇文章就是要讓你入門java多線程!

在這裏插入圖片描述
就在前幾天,有位讀者朋友私信宜春,說期待出一篇多線程的文章,我當時心裏是小鹿亂撞啊....因而這幾天茶不思飯不想,好幾天深夜皆是展轉反側,兩目深凝,以致於這幾天走起路來格外飄飄然,左搖右晃的,魔鬼般的步伐,通常兩步,走在大馬路中央上差點被打~我認可太誇張了,感受又要被打~。最終仍是君意不可違,答應了這位讀者朋友,從這位讀者朋友的博客頭像能夠看的出來,這位朋友絕bi歷經滄桑,對生活無盡的坦然浩對,看透俗世凡塵、世態炎涼、趨炎附勢,擁有着極高的安心恬蕩情懷...啥?啥子?這個是系統默認頭像....嗯嗯嗯呃。。。那個那個宜春啥都沒說哈,別把什麼事都扯宜春身上,大家一每天的,我啥都沒說(義正詞嚴)...java

@程序員

1. 理解線程與進程

因爲併發確定涉及到多線程,所以在進入併發編程主題以前,咱們先來了解一下進程和線程的由來,這對後面對併發編程的理解將會有很大的幫助。面試

進程和線程的對比這一知識點因爲過於基礎,正由於過於基礎,因此咱們更應該透徹它!咱們必須掌握什麼是線程和進程,掌握線程與進程的關係、區別及優缺點 !數據庫

1.一、何爲進程?

首先咱們來看一下進程的概念:編程

進程:是指一個內存中運行的應用程序,每一個進程都有一個獨立的內存空間,一個應用程序能夠同時運行多個進程;進程也是程序的一次執行過程,是系統運行程序的基本單位;系統運行一個程序便是一個進程從建立、運行到消亡的過程。安全

看完以後,是否是感受很抽象?很懵bi?懵bi就對了,說明你和我智商同樣高....~開個玩笑~服務器

不妨先憋棄上面的概念,放鬆一下大腦,雙擊打開LOL,秒選德馬打野,輸了直接退出遊戲而且保持微笑,而後正襟危坐心平氣和的看宜春寫的博客....微信

這個時候的你不只僅是愉快的擼了一把遊戲,並且還親自體驗擼了一把進程...其實在你雙擊打開LOL的時候就已經建立了進程,此話怎講?衆所周知,咱們的電腦安裝的軟件好比:LOL、微信、谷歌等等都是存儲在咱們的硬盤上的,硬盤上的數據能夠說是永久存儲(ORM),當咱們雙擊LOL的時候,LOL程序執行就進入了內存中,全部的程序必須進入內存中才能執行,內存屬於臨時存儲(RAM),而進入內存的程序均可以叫作是進程,把LOL程序退出的時候,LOL程序就會退出內存,進程也就隨之銷燬了!所以說各位擼了一把進程也不爲過吧。多線程

啥?字太多了,看的不夠明瞭,不如看圖得勁....額。。。
在這裏插入圖片描述
上面主要是經過抽象的描述了進程,其實進程是能夠很直觀的看的到的,咱們能夠再電腦底部任務欄,右鍵----->打開任務管理器,能夠查看當前任務的進程:
在這裏插入圖片描述
其實,關於線程博主我徹底能夠一兩句話歸納,可是這樣並不負責,畢竟這篇文章標題就是要讓你完全入門java多線程。若是連進程都理解很差談何完全理解多線程?

1.二、何爲線程?

一樣的,咱們先來看線程的概念

線程是進程中的一個執行單位,負責當前進程中程序的執行。一個進程中至少有一個線程,也就是說一個進程能夠有多個線程的,而多個線程的進程運用程序就叫作多線程程序

線程的概念稍微好理解不少,可是想更深層次的去理解光靠上面一段文字的概述是徹底不夠的!

這不打LOL的過程當中,屬實卡的一批,果真花高價998買的6手戴爾筆記本打LOL屬實像極了愛情。這個時候不得不雙擊打開電腦安全管家進行殺毒,果真2500天沒有進行過病毒查殺,我天。。。其實我相信不少人都用過電腦管家或者手機管家之類的安全軟件,咱們都很清楚咱們開啓病毒查殺以後通常要幾分鐘掃描查殺,這個時候咱們是可讓它後臺進行的,咱們不會等而是開啓另外一個垃圾清理的功能,這個時候咱們也不會等而是再去啓動電腦加速功能。等到 這些操做都完成以後果斷退出電腦管家,繼續LOL,果真高價998買的6手戴爾筆記本再怎麼殺毒打LOL仍是照樣的卡....

其實清楚線程必然涉及到CPU的相關概念了,將上面文字所描述的用圖片歸納,大體爲:
在這裏插入圖片描述
在這裏插入圖片描述

1.三、何爲多線程?

從上一節中,咱們也提到過多線程,因此理解起來應該不難。

多線程就是多個線程同時運行交替運行

單核CPU:交替運行。
多核CPU:同時運行。

其實,多線程程序並不能提升程序的運行速度,但可以提升程序運行效率,讓CPU的使用率更高。

1.四、何爲線程調度優先級?

提及線程調度優先級這個概念,就讓我想到如今咱們大部分人投簡歷同樣。若是你的學歷或者工做經驗越高,那麼你的優先級就越高,面試官很大概率就會讓你去面試但也不是必定只是概率特別大,若是線程的優先級相同,那麼會隨機選擇一個(線程隨機性)!在咱們每一個人的電腦中線程是能夠設置線程的優先級的,可是生活中沒有優先級(學歷、工做經驗)的孩子就只能靠本身的能力了~媽耶,太真實了...~

線程優先級具備繼承特性好比A線程啓動B線程,則B線程的優先級和A是同樣的。

線程優先級具備隨機性也就是說線程優先級高的不必定每一次都先執行完,只是被執行的可能性更大。

在從此的多線程學習旅遊中咱們會使用到getPriority()方法獲取線程的優先級。

1.五、爲何提倡使用多線程而不是多進程?

線程與進程類似,但線程是一個比進程更小的執行單位,是程序執行的最小單位。一個進程在其執行的過程當中能夠產生多個線程。與進程不一樣的是同類的多個線程共享同一塊內存空間和一組系統資源,因此係統在產生一個線程,或是在各個線程之間做切換工做時,負擔要比進程小得多,也正由於如此,線程也被稱爲輕量級進程。同時線程是程序執行的最小單位。使用多線程而不是用多進程去進行併發程序的設計,是由於線程間的切換和調度的成本遠遠小於進程。

而使用多線程,多線程會將程序運行方式從串行運行變爲併發運行,效率會有很大提升。

二、理解並行和併發

在博主認爲併發和並行是兩個很是容易被混淆的概念。爲了防止繞暈你們,因此我選擇長話短說!

  1. 併發:一個時間段內同時發生(並非同時發生)。
  2. 並行:同一時刻發生(真正的同時發生)。

它們均可以表示兩個或者多個任務一塊兒執行,可是偏重點有些不一樣。

於此同時,咱們不妨回顧一下上面所提到過的CPU,並再次理解併發與並行的區別,從而溫故知新 ~我TM簡直是個天才!~

單核CPU:交替運行【併發】
多核CPU:同時運行【並行】

併發給人的感受是同時運行,那是由於分時交替運行的時間是很是短的!

三、特殊的一個單線程:主線程(Main線程)

咱們常說的主線程就是Main線程,它是一個特殊的單線程,話很少說,直接擼碼:

定義一個用於測試的demo類Person

package demo;

public class Person {
   public String name;

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

   public void run(){
       int i=1;
       while (i<5){
           System.out.println(name+i);
           i++;
       }
   }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

編寫Main方法

package demo;

public class MainThreadDemo {
    public static void main(String[] args) {
        Person per=new Person("常威");
        per.run();

        Person Per2=new Person("來福");
        Per2.run();
    }
}

運行結果就已經很顯而易見了,放心我不是靠大家運行結果而是單純的先分析主線程。

運行結果:
    常威1
    常威2
    常威3
    常威4
    來福1
    來福2
    來福3
    來福4

3.一、分析主線程原理

在這裏插入圖片描述

3.二、 單線程的侷限性

單線程不只效率低下,並且存在很大的侷限性,唯一的優勢就是安全。因此說女孩子長得安全其實也是一種優勢,噗哈哈哈...

如何體現出單線程效率低下以及它的侷限性呢?其實只要一句代碼便可,仍是以上面的單線程Main線程爲例:

package demo;

public class MainThreadDemo {
    public static void main(String[] args) {
        Person per=new Person("常威");
        per.run();
        int a=6/0;  //=====================特別注意這行代碼
        Person Per2=new Person("來福");
        Per2.run();
    }
}

試想一下運行結果...
在這裏插入圖片描述
若是對上面的運行結果有問題,或者疑問。那沒錯了,你簡直是個天(小)才(白)!真真的天(小)才(白),頗有可能異常機制沒學好,好吧我給你貼出來:【java基礎之異常】死了都要try,不淋漓盡致地catch我不痛快!

言歸正傳,效率低下何以見得?這是數據少,若是是一億條數據呢,單線程就是一個一個打印。那侷限性又何以見得呢?從上面運行結果來看也能看出,只由於一行代碼而致使下面代碼再也不執行。已經很明顯了。

四、 建立多線程的四種方式

說是說建立多線程有四種方式,但考慮到是入門文章仍是主要寫入門的兩種方式,剩下的兩個暫時忽略。忽略的兩種方法有:實現Callable接口經過FutureTask包裝器來建立Thread線程、使用ExecutorServiceCallableFuture實現有返回結果的線程。如今可能對於入門的童鞋來講是接收不了的,之後再去了解也不晚!

4.一、繼承Thread類

Java使用java.lang.Thread類表明線程,全部的線程對象都必須是Thread類或其子類的實例。每一個線程的做用是完成必定的任務,實際上就是執行一段程序流即一段順序執行的代碼。Java使用線程執行體來表明這段程序流。

Java中經過繼承Thread類來建立啓動多線程的步驟以下:

  1. 定義Thread類的子類,並重寫該類的run()方法,該run()方法的方法體就表明了線程須要完成的任務,所以把run()方法稱爲線程執行體。
  2. 建立Thread子類的實例,即建立了線程對象
  3. 調用線程對象的start()方法來啓動該線程

代碼以下:

測試類:

public class Demo01 {
    public static void main(String[] args) {
        //建立自定義線程對象
        MyThread mt = new MyThread("新的線程!");
        //開啓新線程
        mt.start();
        //在主方法中執行for循環
        for (int i = 0; i < 10; i++) {
            System.out.println("main線程!"+i);
        }
    }
}

自定義線程類:

public class MyThread extends Thread {
    //定義指定線程名稱的構造方法
    public MyThread(String name) {
        //調用父類的String參數的構造方法,指定線程的名稱
        super(name);
    }
    /**
     * 重寫run方法,完成該線程執行的邏輯
     */
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName()+":正在執行!"+i);
        }
    }
}

Thread類本質上是實現了Runnable接口的一個實例,表明一個線程的實例。啓動線程的惟一方法就是經過Thread類的start()實例方法。start()方法是一個native方法,它將啓動一個新線程,並執行run()方法。這種方式實現多線程很簡單,經過本身的類直接extend Thread,並複寫run()方法,就能夠啓動新線程並執行本身定義的run()方法。

4.二、實現Runnable接口

若是本身的類已經繼承另外一個類,就沒法直接繼承Thread,此時,能夠實現一個Runnable接口來建立線程,顯然實現Runnable接口方式建立線程的優點就很明顯了。

直接擼碼:

自定義一個類實現Runnable接口,並重寫接口中的run()方法,併爲run方法添加要執行的代碼方法。

public class RunableDemo implements Runnable{

    @Override
    public void run() {
        int a = 1;
        while (a<20){
            System.out.println(Thread.currentThread().getName()+ a);//Thread.currentThread().getName()爲獲取當前線程的名字
            a++;
        }
    }
}

編寫Main方法

爲了啓動自定義類RunableDemo ,須要首先實例化一個Thread,並傳入RunableDemo 實例

public class MainThreadDemo {

    public static void main(String[] args) {
        RunableDemo runn=new RunableDemo();
        
        //實例化一個Thread並傳入本身的RunableDemo 實例
        Thread thread=new Thread(runn);
        thread.start();

        int a = 1;
        while (a<20){
            //Thread.currentThread().getName()爲獲取當前線程的名字
            System.out.println(Thread.currentThread().getName()+ a);
            a++;
        }
    }
}

運行結果:

main1
main2
main3
Thread-01
Thread-02
Thread-03
Thread-04
Thread-05
Thread-06
....

其實多運行幾遍,你會方法每次運行的結果順序都不同,這主要是因爲多線程會去搶佔CPU的資源,誰搶到了誰就執行,而Main和Thread兩個線程一直在爭搶。

實際上,當傳入一個Runnable target(目標)參數給Thread後,Threadrun()方法就會調用target.run(),參考JDK源代碼:

public void run() {  
  if (target != null) {  
   target.run();  
  }  
}

4.三、兩種入門級建立線程的區別

採用繼承Thread類方式:

(1)優勢:編寫簡單,若是須要訪問當前線程,無需使用Thread.currentThread()方法,直接使用this,便可得到當前線程。
(2)缺點:由於線程類已經繼承了Thread類,因此不能再繼承其餘的父類。

採用實現Runnable接口方式:

(1)優勢:線程類只是實現了Runable接口,還能夠繼承其餘的類。在這種方式下,能夠多個線程共享同一個目標對象,因此很是適合多個相
同線程來處理同一份資源的狀況,從而能夠將CPU代碼和數據分開,造成清晰的模型,較好地體現了面向對象的思想。
(2)缺點:編程稍微複雜,若是須要訪問當前線程,必須使用Thread.currentThread()方法。

小結:
若是一個類繼承Thread,則不適合資源共享。可是若是實現了Runable接口的話,則很容易的實現資源共享。

實現Runnable接口比繼承Thread類的優點:

1.適合多個相同代碼的線程去處理同一個資源。

2.能夠避免java中單繼承的限制。

3.增長代碼的健壯性,實現解耦。代碼能夠被多個線程共享,代碼和數據獨立。

4.線程池中只能放入實現Runnable或Callable類線程,不能放入繼承Thread的類【線程池概念以後會慢慢涉及】

因此,若是選擇哪一種方式,儘可能選擇實現Runnable接口

其實學到後面的線程池,你會發現上面兩種建立線程的方法實際上不多使用,通常都是用線程池的方式比較多一點。使用線程池的方式也是最推薦的一種方式,另外,《阿里巴巴Java開發手冊》在第一章第六節併發處理這一部分也強調到「線程資源必須經過線程池提供,不容許在應用中自行顯示建立線程」。不過處於入門階段的童鞋博主仍是強烈建議一步一個腳印比較好!

五、使用匿名內部類方式建立線程

談起匿名內部類,可能不少小白是比較陌生的,畢竟開發中使用的仍是比較少,可是一樣是很是重要的一個知識!於此同時我就貼出關於匿名內部類的文章程序員你真的理解匿名內部類嗎?若是小白童鞋能看懂下面這個代碼,真的你不須要看那篇文章了,你T喵的簡直是個天才!

package AnonymousInner;

public class NiMingInnerClassThread {
    public static void main(String[] args) {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i<5;i++){
                    System.out.println("熊孩子:"+i);
                }
            }
        };
        new Thread(r).start();
        for (int i = 0; i < 5 ; i++){
            System.out.println("傻狍子:"+i);
        }
    }
}

小白童鞋還愣着幹啥呀趕忙去補補...

六、線程安全問題

線程安全問題主要是共享資源競爭的問題,也就是在多個線程狀況下,一個或多個線程同時搶佔同一資源致使出現的一些沒必要要的問題,最典型的例子就是火車四個窗口售票問題了,這裏就再也不舉售票例子了,已經爛大街了,這裏就簡單實現一個線程安全問題代碼....

實現Runnable接口方式爲例,主要實現過程是:實例化三個Thread,並傳入同一個RunableDemo 實例做爲參數,最後開啓三條相同參數的線程,代碼以下:

public class RunableDemo implements Runnable{
    public int a = 100;//線程共享數據
    
    @Override
    public void run() {
        while (a>0){
            System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
            a--;
        }
    }
}
public class MainThreadDemo {

    public static void main(String[] args) {
        RunableDemo runn=new RunableDemo();
        
        Thread thread1=new Thread(runn);
        Thread thread2=new Thread(runn);
        Thread thread3=new Thread(runn);
        thread1.start();
        thread2.start();
        thread3.start();
        }
 }

運行結果:

Thread-0==100
Thread-0==99
Thread-1==100
Thread-1==97
Thread-1==96
Thread-1==95
Thread-2==98
...

根據結果能夠看出,確實是三條線程(Thread-0、一、2)在執行,安全問題就出在線程會出現相同的結果好比上面的100就出現了兩次,若是循環條件更改一下可能也會出現負數的狀況。這種狀況該怎麼解決呢?這個時候就須要線程同步了!

七、解決線程安全問題:線程同步

實際上,線程安全問題的解決方法有三種:

一、同步代碼塊
二、同步方法
三、鎖機制

7.一、 synchronized同步代碼塊

第一種方法:同步代碼塊

格式:

synchronized(鎖對象) {
可能會出現線程安全問題的代碼(訪問共享數據的代碼)
}

使用同步代碼塊特別注意:
一、經過代碼塊的鎖對象,能夠是任意對象
二、必須保證多個線程使用的鎖對象必須是同一個
三、鎖對象的做用是把同步代碼快鎖住,只容許一個線程在同步代碼塊執行

仍是以上面線程安全問題爲例子,使用同步代碼塊舉例:

public class RunableDemo implements Runnable{
    public int a = 100;//線程共享數據

    Object object=new Object(); //事先準備好一個鎖對象

    @Override
    public void run() {
        synchronized (object){  //使用同步代碼塊
        while (a>0){
            System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
            a--;
        }
        }
    }
}

Main方法沒有任何改動,運行一下結果是絕對沒問題的,數據都是正確的沒有出現重複狀況這一出,各位能夠本身嘗試一下!

同步代碼塊的原理:

使用了一個鎖對象,叫同步鎖,對象鎖,也叫同步監視器,當開啓多個線程的時候,多個線程就開始搶奪CPU的執行權,好比如今t0線程首先的到執行,就會開始執行run方法,遇到同步代碼快,首先檢查是否有鎖對象,發現有,則獲取該鎖對象,執行同步代碼塊中的代碼。以後當CUP切換線程時,好比t1獲得執行,也開始執行run方法,可是遇到同步代碼塊檢查是否有鎖對象時發現沒有鎖對象,t1便被阻塞,等待t0執行完畢同步代碼塊,釋放鎖對象,t1才能夠獲取從而進入同步代碼塊執行。
同步中的線程,沒有執行完畢是不會釋放鎖的,這樣便實現了線程對臨界區的互斥訪問,保證了共享數據安全。
缺點:頻繁的獲取釋放鎖對象,下降程序效率

7.二、同步方法

使用步驟:

一、把訪問了共享數據的代碼抽取出來,放到一個方法中
二、在該方法上添加 synchronized 修飾符

格式:

修飾符 synchronized 返回值類型 方法名稱(參數列表) {
  方法體...
}

代碼示例:

public class RunableDemo implements Runnable{
    public int a = 100;//線程共享數據

    @Override
    public void run() {
        while (true){
            sell(); //調用下面的sell方法
        }
    }
    
    //訪問了共享數據的代碼抽取出來,放到一個方法sell中 
    public synchronized void sell(){
        while (a>0){
            System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
            a--;
        }
    }
}

同步方法的也是同樣鎖住同步的代碼,可是鎖對象的是Runable實現類對象,也就是this,誰調用方法,就是誰。

說到同步方法,就不得不說一下靜態同步方法,顧名思義,就是在同步方法上加上static,靜態的同步方法,添加一個靜態static修飾符,此時鎖對象就不是this了,靜態同步方法的鎖對象是本類的class屬性,class文件對象(反射)

public class RunableDemo implements Runnable{
    public static int a = 100;//線程共享數據     =====此時共享數據也要加上static

    @Override
    public void run() {
        while (true){
            sell();
        }
    }

    public static synchronized void sell(){  //注意添加了static關鍵字
        while (a>0){
            System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
            a--;
        }
    }
}

使用靜態同步方法時,此時共享數據也要加上static,由於static成員才能訪問static成員,若是對static關鍵字不是他別理解的能夠補補了,放心,博主有信心讓你有所收穫,會讓你從新認識到static的魅力:深刻理解static關鍵字

固然靜態同步方法瞭解便可!

7.三、Lock鎖

Lock接口位於java.util.concurrent.locks.Lock它是JDK1.5以後出現的,Lock接口中的方法:

void lock(): 獲取鎖

 

void unlock(): 釋放鎖

Lock接口的一個實現類java.util.concurrent.locks.ReentrantLock implements Lock接口

使用方法:
一、在Runable實現類的成員變量建立一個ReentrantLock對象
二、在可能產生線程安全問題的代碼該對象調用lock方法獲取鎖
三、在可能產生線程安全問題的代碼該對象調用unlock方法釋放鎖

代碼示例:

import java.util.concurrent.locks.ReentrantLock;

public class RunableDemo implements Runnable{
    public static int a = 100;//線程共享數據

    //一、在Runable實現類的成員變量建立一個ReentrantLock對象============
    ReentrantLock reentrantLock = new ReentrantLock();

    @Override
    public void run() {
        // 二、在可能產生線程安全問題的代碼前該對象調用lock方法獲取鎖=======
        reentrantLock.lock();
        while (a>0){
            System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
            a--;
        }
        // 三、在可能產生線程安全問題的代碼後該對象調用unlock方法獲取鎖======
        reentrantLock.unlock();
    }

}

固然更安全的寫法是,在線程安全問題代碼中try...catchy,最後在finally語句中添加reentrantLock.unlock();,這樣方爲上上策!

7.四、三種方法小結

第一種
synchronized 同步代碼塊:能夠是任意的對象必須保證多個線程使用的鎖對象是同一個

 

第二種
synchronized 同步方法: 鎖對象是this,誰調用鎖對象就是誰

 

synchronized 靜態同步方法: 鎖對象是其class對象,該對象能夠用this.getClass()方法獲取,也可使用當前類名.class 表示。【瞭解便可】

 

第三種
Look鎖方法:該方法提供的方法遠遠多於synchronized方式,主要在Runable實現類的成員變量建立一個ReentrantLock對象,並使用該對象調用lock方法獲取鎖以及unlock方法釋放鎖!

八、線程經常使用方法

8.一、Thread類

  Thread():用於構造一個新的Thread。

  Thread(Runnable target):用於構造一個新的Thread,該線程使用了指定target的run方法。

  Thread(ThreadGroup group,Runnable target):用於在指定的線程組中構造一個新的Thread,該

  線程使用了指定target的run方法。

  currentThread():得到當前運行線程的對象引用。

  interrupt():將當前線程置爲中斷狀態。

  sleep(long millis):使當前運行的線程進入睡眠狀態,睡眠時間至少爲指定毫秒數。

  join():等待這個線程結束,即在一個線程中調用other.join(),將等待other線程結束後才繼續本線程。

  yield():當前執行的線程讓出CPU的使用權,從運行狀態進入就緒狀態,讓其餘就緒線程執行。

8.二、Object類

  wait():讓當前線程進入等待阻塞狀態,直到其餘線程調用了此對象的notify()或notifyAll()方法後,當前線程才被喚醒進入就緒狀態。

  notify():喚醒在此對象監控器(鎖對象)上等待的單個線程。

  notifyAll():喚醒在此對象監控器(鎖對象)上等待的全部線程。

注意:wait()、notify()、notifyAll()都依賴於同步鎖,而同步鎖是對象持有的,且每一個對象只有一個,因此這些方法定義在Object類中,而不是Thread類中。

8.三、yield()、sleep()、wait()比較

   wait():讓線程從運行狀態進入等待阻塞狀態,而且會釋放它所持有的同步鎖。

   yield():讓線程從運行狀態進入就緒狀態,不會釋放它鎖持有的同步鎖。

   sleep():讓線程從運行狀態進入阻塞狀態,不會釋放它鎖持有的同步鎖。

九、線程的狀態

在這裏插入圖片描述
以上只是簡單的一個線程狀態圖,其實線程狀態博大精深,要講清楚仍是要一大篇文筆,做爲入門文章先了解一下吧,以後的併發編程文章將再講述吧!

若是想要去深刻了解一下的話也是能夠的:Java線程的6種狀態及切換

十、線程池

在java中只要說到池,基本都是一個套路,啥數據庫鏈接池、jdbc鏈接池等,思想基本上就是:一個容納多個要使用資源的容器,其中的資源能夠反覆使用,省去了頻繁建立線程對象的操做,無需反覆建立資源而消耗過多資源。

10.一、線程池概述

線程池其實就是一個容納多個線程的容器,其中的線程能夠反覆使用,省去了頻繁建立線程對象的操做,無需反覆建立線程而消耗過多資源。

合理利用線程池可以帶來三個好處:

  1. 下降資源消耗。減小了建立和銷燬線程的次數,每一個工做線程均可以被重複利用,可執行多個任務。
  2. 提升響應速度。當任務到達時,任務能夠不須要的等到線程建立就能當即執行。
  3. 提升線程的可管理性。能夠根據系統的承受能力,調整線程池中工做線線程的數目,防止由於消耗過多的內存,而把服務器累趴下(每一個線程須要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)。

10.二、 線程池的使用

Java裏面線程池的最頂級接口是java.util.concurrent.Executor,可是嚴格意義上講Executor並非一個線程池,而只是一個執行線程的工具。真正的線程池接口是java.util.concurrent.ExecutorService

要配置一個線程池是比較複雜的,尤爲是對於線程池的原理不是很清楚的狀況下,頗有可能配置的線程池不是較優的,所以在java.util.concurrent.Executors線程工廠類裏面提供了一些靜態工廠,生成一些經常使用的線程池。官方建議使用Executors工程類來建立線程池對象。

Executors類中有個建立線程池的方法以下:

  • public static ExecutorService newFixedThreadPool(int nThreads):返回線程池對象。(建立的是有界線程池,也就是池中的線程個數能夠指定最大數量)

獲取到了一個線程池ExecutorService 對象,那麼怎麼使用呢,在這裏定義了一個使用線程池對象的方法以下:

  • public Future<?> submit(Runnable task):獲取線程池中的某一個線程對象,並執行

Future接口:用來記錄線程任務執行完畢後產生的結果。線程池建立與使用。

使用線程池中線程對象的步驟:

  1. 建立線程池對象。
  2. 建立Runnable接口子類對象。(task)
  3. 提交Runnable接口子類對象。(take task)
  4. 關閉線程池(通常不操做這一步)。

Runnable實現類代碼:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("我要一個游泳教練");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("教練來了: " + Thread.currentThread().getName());
        System.out.println("教我游泳,教會後,教練又回到了游泳池");
    }
}

線程池測試類:

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 建立線程池對象
        ExecutorService service = Executors.newFixedThreadPool(2);//包含2個線程對象
        // 建立Runnable實例對象
        MyRunnable r = new MyRunnable();

        //本身建立線程對象的方式
        // Thread t = new Thread(r);
        // t.start(); ---> 調用MyRunnable中的run()

        // 從線程池中獲取線程對象,而後調用MyRunnable中的run()
        service.submit(r);
        // 再獲取個線程對象,調用MyRunnable中的run()
        service.submit(r);
        service.submit(r);
        // 注意:submit方法調用結束後,程序並不終止,是由於線程池控制了線程的關閉。
        // 將使用完的線程又歸還到了線程池中
        // 關閉線程池
        //service.shutdown();
    }
}

以上只是簡單的使用線程池,僅僅是入門階段!道阻且長,路還很長....

到這裏,本文章入門暫時告一段落,之後有時間儘可能抽空更新....

若是本文章對你有幫助,哪怕是一點點,請點個讚唄,謝謝你~

歡迎各位關注個人公衆號,一塊兒探討技術,嚮往技術,追求技術...說好了來了就是盆友喔...

在這裏插入圖片描述

相關文章
相關標籤/搜索