Java學(xué)習(xí)筆記(五):線程

foochanehttps://foochane.cn/article/2019123002.html

1 多線程

我們在之前,學(xué)習(xí)的程序在沒有跳轉(zhuǎn)語句的前提下呼寸,都是由上至下依次執(zhí)行对雪,那現(xiàn)在想要設(shè)計(jì)一個(gè)程序瑟捣,邊打游戲邊聽歌迈套,怎么設(shè)計(jì)桑李?

要解決上述問題,咱們得使用多進(jìn)程或者多線程來解決.

1.1 并發(fā)與并行

  • 并發(fā):指兩個(gè)或多個(gè)事件在同一個(gè)時(shí)間段內(nèi)發(fā)生贵白。
  • 并行:指兩個(gè)或多個(gè)事件在同一時(shí)刻發(fā)生(同時(shí)發(fā)生)禁荒。

[圖片上傳失敗...(image-4afbb6-1580720568911)]

在操作系統(tǒng)中寥掐,安裝了多個(gè)程序召耘,并發(fā)指的是在一段時(shí)間內(nèi)宏觀上有多個(gè)程序同時(shí)運(yùn)行污它,這在單 CPU 系統(tǒng)中衫贬,每一時(shí)刻只能有一道程序執(zhí)行固惯,即微觀上這些程序是分時(shí)的交替運(yùn)行葬毫,只不過是給人的感覺是同時(shí)運(yùn)行贴捡,那是因?yàn)榉謺r(shí)交替運(yùn)行的時(shí)間是非常短的烂斋。

而在多個(gè) CPU 系統(tǒng)中汛骂,則這些可以并發(fā)執(zhí)行的程序便可以分配到多個(gè)處理器上(CPU)帘瞭,實(shí)現(xiàn)多任務(wù)并行執(zhí)行图张,即利用每個(gè)處理器來處理一個(gè)可以并發(fā)執(zhí)行的程序,這樣多個(gè)程序便可以同時(shí)執(zhí)行侥钳。目前電腦市場上說的多核 CPU舷夺,便是多核處理器给猾,核越多敢伸,并行處理的程序越多尾序,能大大的提高電腦運(yùn)行的效率每币。

注意:單核處理器的計(jì)算機(jī)肯定是不能并行的處理多個(gè)任務(wù)的兰怠,只能是多個(gè)任務(wù)在單個(gè)CPU上并發(fā)運(yùn)行痕慢。同理,線程也是一樣的掖举,從宏觀角度上理解線程是并行運(yùn)行的塔次,但是從微觀角度上分析卻是串行運(yùn)行的励负,即一個(gè)線程一個(gè)線程的去運(yùn)行,當(dāng)系統(tǒng)只有一個(gè)CPU時(shí)略吨,線程會以某種順序執(zhí)行多個(gè)線程翠忠,我們把這種情況稱之為線程調(diào)度秽之。

1.2 線程與進(jìn)程

  • 進(jìn)程:是指一個(gè)內(nèi)存中運(yùn)行的應(yīng)用程序考榨,每個(gè)進(jìn)程都有一個(gè)獨(dú)立的內(nèi)存空間河质,一個(gè)應(yīng)用程序可以同時(shí)運(yùn)行多個(gè)進(jìn)程云头;進(jìn)程也是程序的一次執(zhí)行過程溃槐,是系統(tǒng)運(yùn)行程序的基本單位昏滴;系統(tǒng)運(yùn)行一個(gè)程序即是一個(gè)進(jìn)程從創(chuàng)建谣殊、運(yùn)行到消亡的過程姻几。

  • 線程:線程是進(jìn)程中的一個(gè)執(zhí)行單元蛇捌,負(fù)責(zé)當(dāng)前進(jìn)程中程序的執(zhí)行,一個(gè)進(jìn)程中至少有一個(gè)線程春贸。一個(gè)進(jìn)程中是可以有多個(gè)線程的萍恕,這個(gè)應(yīng)用程序也可以稱之為多線程程序雄坪。

    簡而言之:一個(gè)程序運(yùn)行后至少有一個(gè)進(jìn)程维哈,一個(gè)進(jìn)程中可以包含多個(gè)線程

我們可以再電腦底部任務(wù)欄,右鍵----->打開任務(wù)管理器,可以查看當(dāng)前任務(wù)的進(jìn)程:

進(jìn)程

[圖片上傳失敗...(image-ff8a13-1580720568911)]

線程

[圖片上傳失敗...(image-b72516-1580720568911)]

線程調(diào)度:

  • 分時(shí)調(diào)度

    所有線程輪流使用 CPU 的使用權(quán)购撼,平均分配每個(gè)線程占用 CPU 的時(shí)間迂求。

  • 搶占式調(diào)度

    優(yōu)先讓優(yōu)先級高的線程使用 CPU,如果線程的優(yōu)先級相同凌盯,那么會隨機(jī)選擇一個(gè)(線程隨機(jī)性)驰怎,Java使用的為搶占式調(diào)度县忌。

    • 設(shè)置線程的優(yōu)先級

    [圖片上傳失敗...(image-77c44e-1580720568911)]

    • 搶占式調(diào)度詳解

      大部分操作系統(tǒng)都支持多進(jìn)程并發(fā)運(yùn)行,現(xiàn)在的操作系統(tǒng)幾乎都支持同時(shí)運(yùn)行多個(gè)程序鸳慈。比如:現(xiàn)在我們上課一邊使用編輯器走芋,一邊使用錄屏軟件翁逞,同時(shí)還開著畫圖板挖函,dos窗口等軟件津畸。此時(shí)肉拓,這些程序是在同時(shí)運(yùn)行暖途,”感覺這些軟件好像在同一時(shí)刻運(yùn)行著“。

      實(shí)際上欺栗,CPU(中央處理器)使用搶占式調(diào)度模式在多個(gè)線程間進(jìn)行著高速的切換纸巷。對于CPU的一個(gè)核而言瘤旨,某個(gè)時(shí)刻,只能執(zhí)行一個(gè)線程祟偷,而 CPU的在多個(gè)線程間切換速度相對我們的感覺要快修肠,看上去就是在同一時(shí)刻運(yùn)行。
      其實(shí)吗伤,多線程程序并不能提高程序的運(yùn)行速度,但能夠提高程序運(yùn)行效率巧号,讓CPU的使用率更高裂逐。

      [圖片上傳失敗...(image-e3f872-1580720568911)]

1.3 使用Thread類創(chuàng)建線程

翻閱API后得知創(chuàng)建線程的方式總共有兩種,一種是繼承Thread類方式掺涛,一種是實(shí)現(xiàn)Runnable接口方式薪缆。

Java使用java.lang.Thread類代表線程,所有的線程對象都必須是Thread類或其子類的實(shí)例减拭。每個(gè)線程的作用是完成一定的任務(wù)拧粪,實(shí)際上就是執(zhí)行一段程序流即一段順序執(zhí)行的代碼。Java使用線程執(zhí)行體來代表這段程序流癣朗。Java中通過繼承Thread類來創(chuàng)建啟動多線程的步驟如下:

  1. 定義Thread類的子類,并重寫該類的run()方法荣暮,該run()方法的方法體就代表了線程需要完成的任務(wù),因此把run()方法稱為線程執(zhí)行體穗酥。
  2. 創(chuàng)建Thread子類的實(shí)例骏啰,即創(chuàng)建了線程對象
  3. 調(diào)用線程對象的start()方法來啟動該線程

代碼如下:

自定義線程類:

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

測試類:

public class Demo01 {
    public static void main(String[] args) {
        //創(chuàng)建自定義線程對象
        MyThread mt = new MyThread("新的線程!");
        //開啟新線程
        mt.start();
        //在主方法中執(zhí)行for循環(huán)
        for (int i = 0; i < 10; i++) {
            System.out.println("main線程翘骂!"+i);
        }
    }
}

Thread 類的常用方法:

java.lang.Thread 類的API中定義了有關(guān)線程的一些方法壁熄,具體如下:

構(gòu)造方法:

  • public Thread() :分配一個(gè)新的線程對象碳竟。
  • public Thread(String name):分配一個(gè)指定名字的新的線程對象草丧。
  • public Thread(Runnable target) :分配一個(gè)帶有指定目標(biāo)新的線程對象。
  • public Thread(Runnable target,String name) :分配一個(gè)帶有指定目標(biāo)新的線程對象并指定名字莹桅。

常用方法:

  • public String getName() :獲取當(dāng)前線程名稱昌执。
  • public void start() :導(dǎo)致此線程開始執(zhí)行; Java虛擬機(jī)調(diào)用此線程的run方法
  • public void run():此線程要執(zhí)行的任務(wù)在此處定義代碼。
  • public static void sleep(long millis) :使當(dāng)前正在執(zhí)行的線程以指定的毫秒數(shù)暫停(暫時(shí)停止執(zhí)行)诈泼。
  • public static Thread currentThread() :返回對當(dāng)前正在執(zhí)行的線程對象的引用懂拾。

1.4 使用Runnable接口創(chuàng)建線程

采用 java.lang.Runnable 也是非常常見的一種,我們只需要重寫run方法即可铐达。
步驟如下:

  1. 定義Runnable接口的實(shí)現(xiàn)類岖赋,并重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執(zhí)行體娶桦。
  2. 創(chuàng)建Runnable實(shí)現(xiàn)類的實(shí)例贾节,并以此實(shí)例作為Thread的target來創(chuàng)建Thread對象,該Thread對象才是真正
    的線程對象衷畦。
  3. 調(diào)用線程對象的start()方法來啟動線程栗涂。

代碼如下:

定義Runnable接口的實(shí)現(xiàn)類

public class MyRunnable implements Runnable{
    @Override    
    public void run() {    
        for (int i = 0; i < 20; i++) {        
            System.out.println(Thread.currentThread().getName()+" "+i);            
        }        
    }    
}

測試類

public class Demo {
    public static void main(String[] args) {
        //創(chuàng)建自定義類對象  線程任務(wù)對象
        MyRunnable mr = new MyRunnable();
        //創(chuàng)建線程對象
        Thread t = new Thread(mr, "小強(qiáng)");
        t.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("旺財(cái) " + i);
        }
    }
}

通過實(shí)現(xiàn) Runnable接口,使得該類有了多線程類的特征祈争。run()方法是多線程程序的一個(gè)執(zhí)行目標(biāo)斤程。所有的多線程代碼都在run方法里面。Thread類實(shí)際上也是實(shí)現(xiàn)了Runnable接口的類菩混。

在啟動的多線程的時(shí)候忿墅,需要先通過Thread類的構(gòu)造方法Thread(Runnable target) 構(gòu)造出對象,然后調(diào)用Thread對象的start()方法來運(yùn)行多線程代碼沮峡。

實(shí)際上所有的多線程代碼都是通過運(yùn)行Thread的start()方法來運(yùn)行的疚脐。因此,不管是繼承Thread類還是實(shí)現(xiàn)Runnable接口來實(shí)現(xiàn)多線程邢疙,最終還是通過Thread的對象的API來控制線程的棍弄,熟悉Thread類的API是進(jìn)行多線程編程的基礎(chǔ)望薄。

tips:

Runnable對象僅僅作為Thread對象的target,Runnable實(shí)現(xiàn)類里包含的run()方法僅作為線程執(zhí)行體呼畸。而實(shí)際的線程對象依然是Thread實(shí)例痕支,只是該Thread線程負(fù)責(zé)執(zhí)行其target的run()方法。

1.5 Thread 和Runnable的區(qū)別

如果一個(gè)類繼承Thread蛮原,則不適合資源共享卧须。但是如果實(shí)現(xiàn)了Runable接口的話,則很容易的實(shí)現(xiàn)資源共享儒陨。

總結(jié):

實(shí)現(xiàn)Runnable接口比繼承Thread類所具有的優(yōu)勢:

  1. 適合多個(gè)相同的程序代碼的線程去共享同一個(gè)資源花嘶。
  2. 可以避免java中的單繼承的局限性。
  3. 增加程序的健壯性框全,實(shí)現(xiàn)解耦操作察绷,代碼可以被多個(gè)線程共享,代碼和線程獨(dú)立津辩。線程池只能放入實(shí)現(xiàn)Runable或Callable類線程,不能直接放入繼承Thread的類容劳。

擴(kuò)充:在java中喘沿,每次程序運(yùn)行至少啟動2個(gè)線程。一個(gè)是main線程竭贩,一個(gè)是垃圾收集線程蚜印。因?yàn)槊慨?dāng)使用java命令執(zhí)行一個(gè)類的時(shí)候,實(shí)際上都會啟動一個(gè)JVM留量,每一個(gè)JVM其實(shí)在就是在操作系統(tǒng)中啟動了一個(gè)進(jìn)程窄赋。

1.6 匿名內(nèi)部類方式實(shí)現(xiàn)線程的創(chuàng)建

使用線程的內(nèi)匿名內(nèi)部類方式,可以方便的實(shí)現(xiàn)每個(gè)線程執(zhí)行不同的線程任務(wù)操作楼熄。使用匿名內(nèi)部類的方式實(shí)現(xiàn)Thread類和Runnable接口忆绰,重寫的run方法:

  public class NoNameInnerClassThread {
    public static void main(String[] args) {
        //1 線程的父類是Thread
        // new MyThread().start();
        new Thread(){
            //重寫run方法,設(shè)置線程任務(wù)
            @Override
            public void run() {
                for (int i = 0; i <20 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+"aaa");
                }
            }
        }.start();

        //2 線程的接口Runnable
        //Runnable r = new RunnableImpl();//多態(tài)
        Runnable r = new Runnable(){
            //重寫run方法,設(shè)置線程任務(wù)
            @Override
            public void run() {
                for (int i = 0; i <20 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+"bbb");
                }
            }
        };
        new Thread(r).start();

        //簡化接口的方式
        new Thread(new Runnable(){
            //重寫run方法,設(shè)置線程任務(wù)
            @Override
            public void run() {
                for (int i = 0; i <20 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+"ccc");
                }
            }
        }).start();
    }
}

1.7 多線程原理

分析如下代碼:

自定義線程類:

 public class MyThread extends Thread{
/*    
 * 利用繼承中的特點(diǎn)     
 *   將線程名稱傳遞  進(jìn)行設(shè)置    
 */    
public MyThread(String name){    
    super(name);        
}    
/*    
 * 重寫run方法    
 *  定義線程要執(zhí)行的代碼    
 */    
public void run(){           
        for (int i = 0; i < 20; i++) { 
            //getName()方法 來自父親            
            System.out.println(getName()+i);            
        }        
    }    
}

測試類:

public class Demo {
    public static void main(String[] args) {
        System.out.println("這里是main線程");  
        MyThread mt = new MyThread("小強(qiáng)");            
        mt.start();//開啟了一個(gè)新的線程    
         for (int i = 0; i < 20; i++) {    
            System.out.println("旺財(cái):"+i);            
         }        
    }    
} 

下面畫個(gè)多線程執(zhí)行時(shí)序圖來體現(xiàn)一下多線程程序的執(zhí)行流程。

流程圖:

[圖片上傳失敗...(image-9dfe1a-1580720568911)]

程序啟動運(yùn)行 main時(shí)候可岂,java虛擬機(jī)啟動一個(gè)進(jìn)程错敢,主線程main在main()調(diào)用時(shí)候被創(chuàng)建。隨著調(diào)用mt的對象的start方法缕粹,另外一個(gè)新的線程也啟動了稚茅,這樣,整個(gè)應(yīng)用就在多線程下運(yùn)行平斩。通過這張圖我們可以很清晰的看到多線程的執(zhí)行流程亚享,那么為什么可以完成并發(fā)執(zhí)行呢?我們再來講一講原理绘面。

多線程執(zhí)行時(shí)欺税,到底在內(nèi)存中是如何運(yùn)行的呢糜芳?以上個(gè)程序?yàn)槔M(jìn)行圖解說明:多線程執(zhí)行時(shí)魄衅,在棧內(nèi)存中峭竣,其實(shí)每一個(gè)執(zhí)行線程都有一片自己所屬的棧內(nèi)存空間。進(jìn)行方法的壓棧和彈棧晃虫。

[圖片上傳失敗...(image-75c3ee-1580720568911)]

當(dāng)執(zhí)行線程的任務(wù)結(jié)束了皆撩,線程自動在棧內(nèi)存中釋放了。但是當(dāng)所有的執(zhí)行線程都結(jié)束了哲银,那么進(jìn)程就結(jié)束了扛吞。

2 線程安全

2.1 線程安全

如果有多個(gè)線程在同時(shí)運(yùn)行,而這些線程可能會同時(shí)運(yùn)行這段代碼荆责。程序每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的滥比,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的做院。

我們通過一個(gè)案例盲泛,演示線程的安全問題:

電影院要賣票,我們模擬電影院的賣票過程键耕。假設(shè)要播放的電影是 “葫蘆娃大戰(zhàn)奧特曼”寺滚,本次電影的座位共100個(gè)(本場電影只能賣100張票)。

我們來模擬電影院的售票窗口屈雄,實(shí)現(xiàn)多個(gè)窗口同時(shí)賣 “葫蘆娃大戰(zhàn)奧特曼”這場電影票(多個(gè)窗口一起賣這100張票)

需要窗口村视,采用線程對象來模擬;需要票酒奶,Runnable接口子類來模擬

模擬票:

public class Ticket implements Runnable {
    private int ticket = 100;
    /*
     * 執(zhí)行賣票操作
     */
    @Override
    public void run() {
        //每個(gè)窗口賣票的操作
        //窗口 永遠(yuǎn)開啟
        while (true) {
            if (ticket > 0) {//有票 可以賣
                //出票操作
                //使用sleep模擬一下出票時(shí)間
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto‐generated catch block
                    e.printStackTrace();
                }
                //獲取當(dāng)前線程對象的名字
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在賣:" + ticket--);
            }
        }
    }
}

測試類:

public class Demo {
    public static void main(String[] args) {    
        //創(chuàng)建線程任務(wù)對象        
        Ticket ticket = new Ticket();        
        //創(chuàng)建三個(gè)窗口對象        
        Thread t1 = new Thread(ticket, "窗口1");        
        Thread t2 = new Thread(ticket, "窗口2");        
        Thread t3 = new Thread(ticket, "窗口3");        

        //同時(shí)賣票        
        t1.start();        
        t2.start();        
        t3.start();        
    }    
}

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

窗口3正在賣:100
窗口2正在賣:-1
窗口1正在賣:0
窗口2正在賣:100

發(fā)現(xiàn)程序出現(xiàn)了兩個(gè)問題:

  1. 相同的票數(shù),比如100這張票被賣了兩回蚁孔。
  2. 不存在的票,比如0票與-1票惋嚎,是不存在的杠氢。

這種問題,幾個(gè)窗口(線程)票數(shù)不同步了瘸彤,這種問題稱為線程不安全修然。

線程安全問題都是由全局變量及靜態(tài)變量引起的。若每個(gè)線程中對全局變量质况、靜態(tài)變量只有讀操作愕宋,而無寫操作,一般來說结榄,這個(gè)全局變量是線程安全的中贝;若有多個(gè)線程同時(shí)執(zhí)行寫操作,一般都需要考慮線程同步臼朗,否則的話就可能影響線程安全邻寿。

當(dāng)我們使用多個(gè)線程訪問同一資源的時(shí)候蝎土,且多個(gè)線程中對資源有寫的操作,就容易出現(xiàn)線程安全問題绣否。

要解決上述多線程并發(fā)訪問一個(gè)資源的安全性問題:也就是解決重復(fù)票與不存在票問題誊涯,Java中提供了同步機(jī)制(synchronized)來解決。

為了保證每個(gè)線程都能正常執(zhí)行原子操作,Java引入了線程同步機(jī)制蒜撮。
那么怎么去使用呢暴构?有三種方式完成同步操作:

  1. 同步代碼塊。

  2. 同步方法段磨。

  3. 鎖機(jī)制取逾。

3.2 同步代碼塊

同步代碼塊 : synchronized 關(guān)鍵字可以用于方法中的某個(gè)區(qū)塊中,表示只對這個(gè)區(qū)塊的資源實(shí)行互斥訪問苹支。

格式:

synchronized(同步鎖){
     需要同步操作的代碼
}

同步鎖:

對象的同步鎖只是一個(gè)概念,可以想象為在對象上標(biāo)記了一個(gè)鎖.

  1. 鎖對象 可以是任意類型砾隅。
  2. 多個(gè)線程對象 要使用同一把鎖。

注意:在任何時(shí)候,最多允許一個(gè)線程擁有同步鎖,誰拿到鎖就進(jìn)入代碼塊,其他的線程只能在外等著(BLOCKED)债蜜。

使用同步代碼塊解決代碼:

public class Ticket implements Runnable {
    private int ticket = 100;

    //創(chuàng)建一個(gè)鎖對象
    Object lock = new Object();

    /*
     * 執(zhí)行賣票操作
     */
    @Override
    public void run() {
        //每個(gè)窗口賣票的操作
        //窗口 永遠(yuǎn)開啟
        while (true) {
            synchronized (lock) {
                if (ticket > 0) {//有票 可以賣
                    //出票操作
                    //使用sleep模擬一下出票時(shí)間
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // TODO Auto‐generated catch block
                        e.printStackTrace();
                    }
                    //獲取當(dāng)前線程對象的名字
                    String name = Thread.currentThread().getName();
                    System.out.println(name + "正在賣:" + ticket--);
                }
            }
        }
    }
}

當(dāng)使用了同步代碼塊后晴埂,上述的線程的安全問題,解決了策幼。

3.3 同步方法

同步方法 :使用synchronized修飾的方法,就叫做同步方法,保證A線程執(zhí)行該方法的時(shí)候,其他線程只能在方法外等著邑时。

格式:

public synchronized void method(){
   可能會產(chǎn)生線程安全問題的代碼 
}

同步鎖是誰?
對于非static方法,同步鎖就是this。
對于static方法,我們使用當(dāng)前方法所在類的字節(jié)碼對象(類名.class)特姐。

使用同步方法代碼如下:

public class Ticket implements Runnable{
    private int ticket = 100;
    /*
     * 執(zhí)行賣票操作
     */
    @Override
    public void run() {
        //每個(gè)窗口賣票的操作
        //窗口 永遠(yuǎn)開啟
        while(true){
            sellTicket();
        }
    }

    /*
     * 鎖對象是誰調(diào)用這個(gè)方法就是誰
     *   隱含鎖對象就是this
     *   相當(dāng)于synchronized (this){}
     */
    public synchronized void sellTicket(){
        if(ticket>0){//有票 可以賣
            //出票操作
            //使用sleep模擬一下出票時(shí)間
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto‐generated catch block
                e.printStackTrace();
            }
            //獲取當(dāng)前線程對象的名字
            String name = Thread.currentThread().getName();
            System.out.println(name+"正在賣:"+ticket--);
        }
    }
}

3.4 Lock 鎖

java.util.concurrent.locks.Lock機(jī)制提供了比synchronized代碼塊和synchronized方法更廣泛的鎖定操作,同步代碼塊/同步方法具有的功能Lock都有,除此之外更強(qiáng)大,更體現(xiàn)面向?qū)ο蟆?/p>

Lock鎖也稱同步鎖,加鎖與釋放鎖方法化了黍氮,如下:

  • public void lock() :加同步鎖唐含。

  • public void unlock() :釋放同步鎖。

使用如下

public class Ticket implements Runnable{
    private int ticket = 100;

    Lock lock = new ReentrantLock();
    /*
     * 執(zhí)行賣票操作
     */
    @Override
    public void run() {
        //每個(gè)窗口賣票的操作
        //窗口 永遠(yuǎn)開啟
        while(true){
            lock.lock();
            if(ticket>0){//有票 可以賣
                //出票操作
                //使用sleep模擬一下出票時(shí)間
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    // TODO Auto‐generated catch block
                    e.printStackTrace();
                }
                //獲取當(dāng)前線程對象的名字
                String name = Thread.currentThread().getName();
                System.out.println(name+"正在賣:"+ticket--);
            }
            lock.unlock();
            
//            //另一種寫法沫浆,將unlock放在finally里面
//            lock.lock();
//            if(ticket>0){//有票 可以賣
//                //出票操作
//                //使用sleep模擬一下出票時(shí)間
//                try {
//                    Thread.sleep(10);
//                    //獲取當(dāng)前線程對象的名字
//                    String name = Thread.currentThread().getName();
//                    System.out.println(name+"正在賣:"+ticket--);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                } finally {
//                    lock.unlock();
//                }
//            }
//        }
        }
    }
}

3 線程狀態(tài)

3.1 線程狀態(tài)概述

當(dāng)線程被創(chuàng)建并啟動以后捷枯,它既不是一啟動就進(jìn)入了執(zhí)行狀態(tài),也不是一直處于執(zhí)行狀態(tài)专执。在線程的生命周期中淮捆,有幾種狀態(tài)呢?在API中 java.lang.Thread.State 這個(gè)枚舉中給出了六種線程狀態(tài):

這里先列出各個(gè)線程狀態(tài)發(fā)生的條件本股,下面將會對每種狀態(tài)進(jìn)行詳細(xì)解析

線程狀態(tài) 導(dǎo)致狀態(tài)發(fā)生條件
NEW(新建) 線程剛被創(chuàng)建攀痊,但是并未啟動。還沒調(diào)用start方法拄显。
Runnable(可運(yùn)行) 線程可以在java虛擬機(jī)中運(yùn)行的狀態(tài)苟径,可能正在運(yùn)行自己代碼,也可能沒有躬审,這取決于操作系統(tǒng)處理器棘街。
Blocked(鎖阻塞) 當(dāng)一個(gè)線程試圖獲取一個(gè)對象鎖蟆盐,而該對象鎖被其他的線程持有,則該線程進(jìn)入Blocked狀態(tài)遭殉;當(dāng)該線程持有鎖時(shí)石挂,該線程將變成Runnable狀態(tài)。
Waiting(無限等待) 一個(gè)線程在等待另一個(gè)線程執(zhí)行一個(gè)(喚醒)動作時(shí)险污,該線程進(jìn)入Waiting狀態(tài)痹愚。進(jìn)入這個(gè)狀態(tài)后是不能自動喚醒的,必須等待另一個(gè)線程調(diào)用notify或者notifyAll方法才能夠喚醒罗心。
Timed Waiting(計(jì)時(shí)等待) 同waiting狀態(tài)里伯,有幾個(gè)方法有超時(shí)參數(shù),調(diào)用他們將進(jìn)入Timed Waiting狀態(tài)渤闷。這一狀態(tài)將一直保持到超時(shí)期滿或者接收到喚醒通知疾瓮。帶有超時(shí)參數(shù)的常用方法有Thread.sleep 、Object.wait飒箭。
Teminated(被終止) 因?yàn)閞un方法正常退出而死亡狼电,或者因?yàn)闆]有捕獲的異常終止了run方法而死亡。

我們不需要去研究這幾種狀態(tài)的實(shí)現(xiàn)原理弦蹂,我們只需知道在做線程操作中存在這樣的狀態(tài)肩碟。那我們怎么去理解這幾個(gè)狀態(tài)呢,新建與被終止還是很容易理解的凸椿,我們就研究一下線程從Runnable(可運(yùn)行)狀態(tài)與非運(yùn)行狀態(tài)之間的轉(zhuǎn)換問題削祈。

3.2 Timed Waiting (計(jì)時(shí)等待)

Timed Waiting在API中的描述為:一個(gè)正在限時(shí)等待另一個(gè)線程執(zhí)行一個(gè)(喚醒)動作的線程處于這一狀態(tài)。單獨(dú)的去理解這句話脑漫,真是玄之又玄髓抑,其實(shí)我們在之前的操作中已經(jīng)接觸過這個(gè)狀態(tài)了,在哪里呢优幸?

在我們寫賣票的案例中吨拍,為了減少線程執(zhí)行太快,現(xiàn)象不明顯等問題网杆,我們在run方法中添加了sleep語句羹饰,這樣就強(qiáng)制當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行),以“減慢線程”碳却。

其實(shí)當(dāng)我們調(diào)用了sleep方法之后队秩,當(dāng)前執(zhí)行的線程就進(jìn)入到“休眠狀態(tài)”,其實(shí)就是所謂的Timed Waiting(計(jì)時(shí)等待)追城,那么我們通過一個(gè)案例加深對該狀態(tài)的一個(gè)理解刹碾。

實(shí)現(xiàn)一個(gè)計(jì)數(shù)器,計(jì)數(shù)到100座柱,在每個(gè)數(shù)字之間暫停1秒迷帜,每隔10個(gè)數(shù)字出一個(gè)字符串

代碼:

 public class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 100; i++) {
            if ((i) % 10 == 0) {
                System.out.println("‐‐‐‐‐‐‐" + i);
                }
            System.out.print(i);
            try {
                Thread.sleep(1000);
               System.out.print("    線程睡眠1秒物舒!\n");  
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        new MyThread().start();
    }
}

通過案例可以發(fā)現(xiàn), sleep方法的使用還是很簡單的戏锹。我們需要記住下面幾點(diǎn):

  1. 進(jìn)入 TIMED_WAITING 狀態(tài)的一種常見情形是調(diào)用的 sleep 方法冠胯,單獨(dú)的線程也可以調(diào)用,不一定非要有協(xié)
    作關(guān)系锦针。
  2. 為了讓其他線程有機(jī)會執(zhí)行荠察,可以將Thread.sleep()的調(diào)用放線程run()之內(nèi)。這樣才能保證該線程執(zhí)行過程
    中會睡眠
  3. sleep與鎖無關(guān)奈搜,線程睡眠到期自動蘇醒悉盆,并返回到Runnable(可運(yùn)行)狀態(tài)。

小提示:sleep()中指定的時(shí)間是線程不會運(yùn)行的最短時(shí)間馋吗。因此蛛勉,sleep()方法不能保證該線程睡眠到期后就
開始立刻執(zhí)行中姜。

Timed Waiting 線程狀態(tài)圖:

[圖片上傳失敗...(image-4896a9-1580720568911)]

3.3 BLOCKED (鎖阻塞)

Blocked 狀態(tài)在API中的介紹為:一個(gè)正在阻塞等待一個(gè)監(jiān)視器鎖(鎖對象)的線程處于這一狀態(tài)址貌。

我們已經(jīng)學(xué)完同步機(jī)制骂因,那么這個(gè)狀態(tài)是非常好理解的了。比如绍哎,線程A與線程B代碼中使用同一鎖来农,如果線程A獲取到鎖,線程A進(jìn)入到Runnable狀態(tài)崇堰,那么線程B就進(jìn)入到Blocked鎖阻塞狀態(tài)沃于。

這是由Runnable狀態(tài)進(jìn)入Blocked狀態(tài)。除此Waiting以及Time Waiting狀態(tài)也會在某種情況下進(jìn)入阻塞狀態(tài)海诲,而這部分內(nèi)容作為擴(kuò)充知識點(diǎn)帶領(lǐng)大家了解一下揽涮。

Blocked 線程狀態(tài)圖

[圖片上傳失敗...(image-84a5a7-1580720568911)]

3.4 Waiting (無限等待)

Wating狀態(tài)在API中介紹為:一個(gè)正在無限期等待另一個(gè)線程執(zhí)行一個(gè)特別的(喚醒)動作的線程處于這一狀態(tài)。

那么我們之前遇到過這種狀態(tài)嗎饿肺?答案是并沒有,但并不妨礙我們進(jìn)行一個(gè)簡單深入的了解盾似。我們通過一段代碼來學(xué)習(xí)一下:

  public class WaitingTest {
    public static Object obj = new Object();
    public static void main(String[] args) {
        // 演示waiting
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    synchronized (obj){
                        try {
                            System.out.println( Thread.currentThread().getName() +"=== 獲取到鎖對象敬辣,調(diào)用wait方法,進(jìn)入waiting狀態(tài)零院,釋放鎖對象");
                            obj.wait();  //無限等待
                            //obj.wait(5000); //計(jì)時(shí)等待, 5秒 時(shí)間到溉跃,自動醒來
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println( Thread.currentThread().getName() + "=== 從waiting狀態(tài)醒來,獲取到鎖對象告抄,繼續(xù)執(zhí)行了");
                    }
                }
            }
        },"等待線程").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
//                while (true){   //每隔3秒 喚醒一次
                    try {
                        System.out.println( Thread.currentThread().getName() +"‐‐‐‐‐ 等待3秒鐘");
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj){
                        System.out.println( Thread.currentThread().getName() +"‐‐‐‐‐ 獲取到鎖對象,調(diào)用notify方法撰茎,釋放鎖對象");
                        obj.notify();
                    }
                }
//            }
        },"喚醒線程").start();
    }
}
                                               

通過上述案例我們會發(fā)現(xiàn),一個(gè)調(diào)用了某個(gè)對象的 Object.wait 方法的線程會等待另一個(gè)線程調(diào)用此對象的Object.notify()方法 或 Object.notifyAll()方法打洼。

其實(shí)waiting狀態(tài)并不是一個(gè)線程的操作龄糊,它體現(xiàn)的是多個(gè)線程間的通信逆粹,可以理解為多個(gè)線程之間的協(xié)作關(guān)系,多個(gè)線程會爭取鎖炫惩,同時(shí)相互之間又存在協(xié)作關(guān)系僻弹。就好比在公司里你和你的同事們,你們可能存在晉升時(shí)的競爭他嚷,但更多時(shí)候你們更多是一起合作以完成某些任務(wù)蹋绽。

當(dāng)多個(gè)線程協(xié)作時(shí),比如A筋蓖,B線程卸耘,如果A線程在Runnable(可運(yùn)行)狀態(tài)中調(diào)用了wait()方法那么A線程就進(jìn)入了Waiting(無限等待)狀態(tài),同時(shí)失去了同步鎖粘咖。假如這個(gè)時(shí)候B線程獲取到了同步鎖蚣抗,在運(yùn)行狀態(tài)中調(diào)用了notify()方法,那么就會將無限等待的A線程喚醒涂炎。注意是喚醒忠聚,如果獲取到鎖對象,那么A線程喚醒后就進(jìn)入Runnable(可運(yùn)行)狀態(tài)唱捣;如果沒有獲取鎖對象两蟀,那么就進(jìn)入到Blocked(鎖阻塞狀態(tài))。

Waiting 線程狀態(tài)圖

[圖片上傳失敗...(image-16ee09-1580720568911)]

3.5 補(bǔ)充知識點(diǎn)

到此為止我們已經(jīng)對線程狀態(tài)有了基本的認(rèn)識震缭,想要有更多的了解赂毯,詳情可以見下圖:

[圖片上傳失敗...(image-a962c0-1580720568912)]

tips:
我們在翻閱API的時(shí)候會發(fā)現(xiàn)Timed Waiting(計(jì)時(shí)等待) 與 Waiting(無限等待) 狀態(tài)聯(lián)系還是很緊密的,比如Waiting(無限等待) 狀態(tài)中wait方法是空參的拣宰,而timed waiting(計(jì)時(shí)等待) 中wait方法是帶參的党涕。這種帶參的方法,其實(shí)是一種倒計(jì)時(shí)操作巡社,相當(dāng)于我們生活中的小鬧鐘膛堤,我們設(shè)定好時(shí)間,到時(shí)通知晌该,可是如果提前得到(喚醒)通知肥荔,那么設(shè)定好時(shí)間在通知也就顯得多此一舉了,那么這種設(shè)計(jì)方案其實(shí)是一舉兩得朝群。如果沒有得到(喚醒)通知燕耿,那么線程就處于Timed Waiting狀態(tài),直到倒計(jì)時(shí)完畢自動醒來;如果在倒計(jì)時(shí)期間得到(喚醒)通知姜胖,那么線程從Timed Waiting狀態(tài)立刻喚醒誉帅。

4 等待喚醒機(jī)制

4.1 線程間通信

概念:多個(gè)線程在處理同一個(gè)資源,但是處理的動作(線程的任務(wù))卻不相同。

比如:線程A用來生成包子的蚜锨,線程B用來吃包子的档插,包子可以理解為同一資源,線程A與線程B處理的動作踏志,一個(gè)是生產(chǎn)阀捅,一個(gè)是消費(fèi),那么線程A與線程B之間就存在線程通信問題针余。

[圖片上傳失敗...(image-4799c3-1580720568912)]

為什么要處理線程間通信:

多個(gè)線程并發(fā)執(zhí)行時(shí), 在默認(rèn)情況下CPU是隨機(jī)切換線程的饲鄙,當(dāng)我們需要多個(gè)線程來共同完成一件任務(wù),并且我們希望他們有規(guī)律的執(zhí)行, 那么多線程之間需要一些協(xié)調(diào)通信圆雁,以此來幫我們達(dá)到多線程共同操作一份數(shù)據(jù)忍级。

如何保證線程間通信有效利用資源:

多個(gè)線程在處理同一個(gè)資源,并且任務(wù)不同時(shí)伪朽,需要線程通信來幫助解決線程之間對同一個(gè)變量的使用或操作轴咱。 就是多個(gè)線程在操作同一份數(shù)據(jù)時(shí), 避免對同一共享變量的爭奪烈涮。也就是我們需要通過一定的手段使各個(gè)線程能有效的利用資源朴肺。而這種手段即—— 等待喚醒機(jī)制。

4.2 等待喚醒機(jī)制

什么是等待喚醒機(jī)制

這是多個(gè)線程間的一種協(xié)作機(jī)制坚洽。談到線程我們經(jīng)常想到的是線程間的競爭(race)戈稿,比如去爭奪鎖,但這并不是故事的全部讶舰,線程間也會有協(xié)作機(jī)制鞍盗。就好比在公司里你和你的同事們,你們可能存在在晉升時(shí)的競爭跳昼,但更多時(shí)候你們更多是一起合作以完成某些任務(wù)般甲。

就是在一個(gè)線程進(jìn)行了規(guī)定操作后,就進(jìn)入等待狀態(tài)(wait())鹅颊, 等待其他線程執(zhí)行完他們的指定代碼過后 再將其喚醒(notify());在有多個(gè)線程進(jìn)行等待時(shí)敷存, 如果需要,可以使用 notifyAll()來喚醒所有的等待線程堪伍。

wait/notify 就是線程間的一種協(xié)作機(jī)制历帚。

等待喚醒中的方法

等待喚醒機(jī)制就是用于解決線程間通信的問題的,使用到的3個(gè)方法的含義如下:

  1. wait:線程不再活動杠娱,不再參與調(diào)度,進(jìn)入 wait set 中谱煤,因此不會浪費(fèi) CPU 資源摊求,也不會去競爭鎖了,這時(shí)的線程狀態(tài)即是 WAITING刘离。它還要等著別的線程執(zhí)行一個(gè)特別的動作室叉,也即是“通知(notify)”在這個(gè)對象上等待的線程從wait set 中釋放出來睹栖,重新進(jìn)入到調(diào)度隊(duì)列(ready queue)中
  2. notify:則選取所通知對象的 wait set 中的一個(gè)線程釋放;例如茧痕,餐館有空位置后野来,等候就餐最久的顧客最先入座。
  3. notifyAll:則釋放所通知對象的 wait set 上的全部線程踪旷。

注意:

哪怕只通知了一個(gè)等待的線程曼氛,被通知線程也不能立即恢復(fù)執(zhí)行,因?yàn)樗?dāng)初中斷的地方是在同步塊內(nèi)令野,而此刻它已經(jīng)不持有鎖舀患,所以她需要再次嘗試去獲取鎖(很可能面臨其它線程的競爭),成功后才能在當(dāng)初調(diào)用 wait 方法之后的地方恢復(fù)執(zhí)行气破。

總結(jié)如下:

  • 如果能獲取鎖聊浅,線程就從 WAITING 狀態(tài)變成 RUNNABLE 狀態(tài);
  • 否則现使,從 wait set 出來低匙,又進(jìn)入 entry set,線程就從 WAITING 狀態(tài)又變成 BLOCKED 狀態(tài)

調(diào)用wait和notify方法需要注意的細(xì)節(jié)

  1. wait方法與notify方法必須要由同一個(gè)鎖對象調(diào)用碳锈。因?yàn)椋簩?yīng)的鎖對象可以通過notify喚醒使用同一個(gè)鎖對象調(diào)用的wait方法后的線程顽冶。
  2. wait方法與notify方法是屬于Object類的方法的。因?yàn)椋烘i對象可以是任意對象殴胧,而任意對象的所屬類都是繼承了Object類的渗稍。
  3. wait方法與notify方法必須要在同步代碼塊或者是同步函數(shù)中使用。因?yàn)椋罕仨氁ㄟ^鎖對象調(diào)用這2個(gè)方法团滥。

4.3 生產(chǎn)者與消費(fèi)者問題

等待喚醒機(jī)制其實(shí)就是經(jīng)典的“生產(chǎn)者與消費(fèi)者”的問題竿屹。

就拿生產(chǎn)包子消費(fèi)包子來說等待喚醒機(jī)制如何有效利用資源:

包子鋪線程生產(chǎn)包子,吃貨線程消費(fèi)包子灸姊。當(dāng)包子沒有時(shí)(包子狀態(tài)為false)拱燃,吃貨線程等待,包子鋪線程生產(chǎn)包子(即包子狀態(tài)為true)力惯,并通知吃貨線程(解除吃貨的等待狀態(tài)),因?yàn)橐呀?jīng)有包子了碗誉,那么包子鋪線程進(jìn)入等待狀態(tài)。接下來父晶,吃貨線程能否進(jìn)一步執(zhí)行則取決于鎖的獲取情況哮缺。如果吃貨獲取到鎖,那么就執(zhí)行吃包子動作甲喝,包子吃完(包子狀態(tài)為false)尝苇,并通知包子鋪線程(解除包子鋪的等待狀態(tài)),吃貨線程進(jìn)入等待。包子鋪線程能否進(jìn)一步執(zhí)行則取決于鎖的獲取情況。

代碼演示:

包子資源類:

public class BaoZi {
     String  pier ;
     String  xianer ;
     boolean  flag = false ;//包子資源 是否存在  包子資源狀態(tài)
}

吃貨線程類:

public class ChiHuo extends Thread{
    private BaoZi bz;

    public ChiHuo(String name,BaoZi bz){
        super(name);
        this.bz = bz;
    }
    @Override
    public void run() {
        while(true){
            synchronized (bz){
                if(bz.flag == false){//沒包子
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("吃貨正在吃"+bz.pier+bz.xianer+"包子");
                bz.flag = false;
                bz.notify();
            }
        }
    }
}

包子鋪線程類:

public class BaoZiPu extends Thread {

    private BaoZi bz;

    public BaoZiPu(String name,BaoZi bz){
        super(name);
        this.bz = bz;
    }

    @Override
    public void run() {
        int count = 0;
        //造包子
        while(true){
            //同步
            synchronized (bz){
                if(bz.flag == true){//包子資源  存在
                    try {

                        bz.wait();

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                // 沒有包子  造包子
                System.out.println("包子鋪開始做包子");
                if(count%2 == 0){
                    // 冰皮  五仁
                    bz.pier = "冰皮";
                    bz.xianer = "五仁";
                }else{
                    // 薄皮  牛肉大蔥
                    bz.pier = "薄皮";
                    bz.xianer = "牛肉大蔥";
                }
                count++;

                bz.flag=true;
                System.out.println("包子造好了:"+bz.pier+bz.xianer);
                System.out.println("吃貨來吃吧");
                //喚醒等待線程 (吃貨)
                bz.notify();
            }
        }
    }
}

測試類:

public class Demo {
    public static void main(String[] args) {
        //等待喚醒案例
        BaoZi bz = new BaoZi();

        ChiHuo ch = new ChiHuo("吃貨",bz);
        BaoZiPu bzp = new BaoZiPu("包子鋪",bz);

        ch.start();
        bzp.start();
    }
}

執(zhí)行效果:

包子鋪開始做包子
包子造好了:冰皮五仁
吃貨來吃吧
吃貨正在吃冰皮五仁包子
包子鋪開始做包子
包子造好了:薄皮牛肉大蔥
吃貨來吃吧
吃貨正在吃薄皮牛肉大蔥包子
包子鋪開始做包子
包子造好了:冰皮五仁
吃貨來吃吧
吃貨正在吃冰皮五仁包子

5 線程池

5.1 線程池思想概述

[圖片上傳失敗...(image-c7bfc4-1580720568912)]

我們使用線程的時(shí)候就去創(chuàng)建一個(gè)線程糠溜,這樣實(shí)現(xiàn)起來非常簡便淳玩,但是就會有一個(gè)問題:

如果并發(fā)的線程數(shù)量很多,并且每個(gè)線程都是執(zhí)行一個(gè)時(shí)間很短的任務(wù)就結(jié)束了非竿,這樣頻繁創(chuàng)建線程就會大大降低系統(tǒng)的效率蜕着,因?yàn)轭l繁創(chuàng)建線程和銷毀線程需要時(shí)間。

那么有沒有一種辦法使得線程可以復(fù)用红柱,就是執(zhí)行完一個(gè)任務(wù)承匣,并不被銷毀,而是可以繼續(xù)執(zhí)行其他的任務(wù)豹芯?

在Java中可以通過線程池來達(dá)到這樣的效果悄雅。今天我們就來詳細(xì)講解一下Java的線程池。

5.2 線程池概念

  • 線程池:其實(shí)就是一個(gè)容納多個(gè)線程的容器铁蹈,其中的線程可以反復(fù)使用宽闲,省去了頻繁創(chuàng)建線程對象的操作,無需反復(fù)創(chuàng)建線程而消耗過多資源握牧。

由于線程池中有很多操作都是與優(yōu)化資源相關(guān)的容诬,我們在這里就不多贅述。我們通過一張圖來了解線程池的工作原理:

[圖片上傳失敗...(image-bb787a-1580720568912)]

合理利用線程池能夠帶來三個(gè)好處:

  1. 降低資源消耗沿腰。減少了創(chuàng)建和銷毀線程的次數(shù)览徒,每個(gè)工作線程都可以被重復(fù)利用,可執(zhí)行多個(gè)任務(wù)颂龙。
  2. 提高響應(yīng)速度习蓬。當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要的等到線程創(chuàng)建就能立即執(zhí)行措嵌。
  3. 提高線程的可管理性躲叼。可以根據(jù)系統(tǒng)的承受能力企巢,調(diào)整線程池中工作線線程的數(shù)目枫慷,防止因?yàn)橄倪^多的內(nèi)存,而把服務(wù)器累趴下(每個(gè)線程需要大約1MB內(nèi)存浪规,線程開的越多或听,消耗的內(nèi)存也就越大,最后死機(jī))笋婿。

5.3 線程池的使用

Java里面線程池的頂級接口是java.util.concurrent.Executor誉裆,但是嚴(yán)格意義上講Executor并不是一個(gè)線程池,而只是一個(gè)執(zhí)行線程的工具缸濒。真正的線程池接口是java.util.concurrent.ExecutorService找御。

要配置一個(gè)線程池是比較復(fù)雜的元镀,尤其是對于線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優(yōu)的霎桅,因此在java.util.concurrent.Executors線程工廠類里面提供了一些靜態(tài)工廠,生成一些常用的線程池讨永。官方建議使用Executors工程類來創(chuàng)建線程池對象滔驶。

Executors類中有個(gè)創(chuàng)建線程池的方法如下:

  • public static ExecutorService newFixedThreadPool(int nThreads):返回線程池對象。(創(chuàng)建的是有界線程池,也就是池中的線程個(gè)數(shù)可以指定最大數(shù)量)

獲取到了一個(gè)線程池ExecutorService 對象卿闹,那么怎么使用呢揭糕,在這里定義了一個(gè)使用線程池對象的方法如下:

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

    Future接口:用來記錄線程任務(wù)執(zhí)行完畢后產(chǎn)生的結(jié)果锻霎。線程池創(chuàng)建與使用著角。

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

  1. 創(chuàng)建線程池對象。
  2. 創(chuàng)建Runnable接口子類對象旋恼。(task)
  3. 提交Runnable接口子類對象吏口。(take task)
  4. 關(guān)閉線程池(一般不做)。

Runnable實(shí)現(xiàn)類代碼:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("我要一個(gè)教練");
        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) {
        // 創(chuàng)建線程池對象
        ExecutorService service = Executors.newFixedThreadPool(2);//包含2個(gè)線程對象
        // 創(chuàng)建Runnable實(shí)例對象
        MyRunnable r = new MyRunnable();

        //自己創(chuàng)建線程對象的方式
        // Thread t = new Thread(r);
        // t.start(); ---> 調(diào)用MyRunnable中的run()

        // 從線程池中獲取線程對象,然后調(diào)用MyRunnable中的run()
        service.submit(r);
        // 再獲取個(gè)線程對象产徊,調(diào)用MyRunnable中的run()
        service.submit(r);
        service.submit(r);
        // 注意:submit方法調(diào)用結(jié)束后,程序并不終止蜀细,是因?yàn)榫€程池控制了線程的關(guān)閉舟铜。
        // 將使用完的線程又歸還到了線程池中
        // 關(guān)閉線程池
        //service.shutdown();
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市奠衔,隨后出現(xiàn)的幾起案子谆刨,更是在濱河造成了極大的恐慌,老刑警劉巖归斤,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痊夭,死亡現(xiàn)場離奇詭異,居然都是意外死亡官册,警方通過查閱死者的電腦和手機(jī)生兆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膝宁,“玉大人鸦难,你說我怎么就攤上這事≡币” “怎么了合蔽?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長介返。 經(jīng)常有香客問我拴事,道長沃斤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任刃宵,我火速辦了婚禮衡瓶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘牲证。我一直安慰自己哮针,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布坦袍。 她就那樣靜靜地躺著十厢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捂齐。 梳的紋絲不亂的頭發(fā)上蛮放,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天,我揣著相機(jī)與錄音奠宜,去河邊找鬼包颁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛挎塌,可吹牛的內(nèi)容都是我干的徘六。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼榴都,長吁一口氣:“原來是場噩夢啊……” “哼待锈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嘴高,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤竿音,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后拴驮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體春瞬,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年套啤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宽气。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡潜沦,死狀恐怖萄涯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情唆鸡,我是刑警寧澤涝影,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站争占,受9級特大地震影響燃逻,放射性物質(zhì)發(fā)生泄漏序目。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一伯襟、第九天 我趴在偏房一處隱蔽的房頂上張望猿涨。 院中可真熱鬧,春花似錦姆怪、人聲如沸嘿辟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至英古,卻和暖如春淀衣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背召调。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工膨桥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人唠叛。 一個(gè)月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓只嚣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親艺沼。 傳聞我的和親對象是個(gè)殘疾皇子册舞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評論 2 354