Java編程思想第21章并發(fā)讀書筆記(上)

學(xué)習(xí)資料:

  • Java編程思想

并發(fā)末誓,在操作系統(tǒng)中,是指一個(gè)時(shí)間段中有幾個(gè)程序都處于已啟動(dòng)運(yùn)行到運(yùn)行完畢之間,且這幾個(gè)程序都是在同一個(gè)處理機(jī)上運(yùn)行蝗碎,但任一個(gè)時(shí)刻點(diǎn)上只有一個(gè)程序在處理機(jī)上運(yùn)行

1. 并發(fā)的多面性

并發(fā)性,又稱共行性旗扑,是指能處理多個(gè)同時(shí)性活動(dòng)的能力蹦骑。并發(fā)的實(shí)質(zhì)是一個(gè)物理CPU(也可以多個(gè)物理CPU)在若干道程序之間多路復(fù)用,并發(fā)性是對(duì)有限物理資源強(qiáng)制行使多用戶共享以提高效率

摘自網(wǎng)絡(luò)

順序編程:程序中所有的事物在任意時(shí)刻都只能執(zhí)行一個(gè)步驟

目前計(jì)算機(jī)都是多核處理器臀防,但并發(fā)通常是提高運(yùn)行在單處理器上的程序的性能

由于在運(yùn)行期間增加了上下文切換的代價(jià)(從一個(gè)任務(wù)切換到另一任務(wù))眠菇,在單處理器運(yùn)行的并發(fā)程序開銷比該程序所有部分的都順序執(zhí)行的開銷大

但由于阻塞的存在边败,并發(fā)就比順序要高效穩(wěn)定


通常一個(gè)線程有5個(gè)狀態(tài):創(chuàng)建,就緒捎废,運(yùn)行笑窜,阻塞,銷毀

阻塞:在程序運(yùn)行中缕坎,某一時(shí)刻怖侦,某一個(gè)任務(wù)由于一些控制范圍之外的條件(通常是I/O)而導(dǎo)致不能繼續(xù)執(zhí)行,通常說(shuō)任務(wù)或者線程阻塞

當(dāng)發(fā)生阻塞時(shí)谜叹,順序編程的整個(gè)程序就會(huì)停止匾寝,直至導(dǎo)致阻塞的外部條件發(fā)生改變;并發(fā)編程的程序荷腊,當(dāng)一個(gè)任務(wù)發(fā)生阻塞時(shí)艳悔,程序中其他的任務(wù)還可以繼續(xù)執(zhí)行,整個(gè)程序依然可以保持繼續(xù)向前執(zhí)行

從性能的角度女仰,如果任務(wù)不會(huì)發(fā)生阻塞猜年,此任務(wù)在單處理器機(jī)器上使用并發(fā)就沒(méi)有意義

使用并發(fā)的一個(gè)極佳需求便是:產(chǎn)生可響應(yīng)的用戶界面。在單核時(shí)代疾忍,程序需要連續(xù)執(zhí)行任務(wù)乔外,同時(shí)要求能夠響應(yīng)用戶界面的控制,以便程序來(lái)響應(yīng)用戶一罩,同時(shí)既要做這又要照顧那杨幼,并發(fā)會(huì)造成聽起來(lái)CPU同時(shí)能夠同時(shí)處于兩個(gè)空間的錯(cuò)覺(jué)。但目前早以是多核時(shí)代聂渊,這種需求順理成章


實(shí)現(xiàn)并發(fā)最直接的方式就是在操作系統(tǒng)級(jí)別使用進(jìn)程差购,進(jìn)程是運(yùn)行在自己的地址空間內(nèi)的自包容的程序

進(jìn)程(Process)是計(jì)算機(jī)中的程序關(guān)于某數(shù)據(jù)集合上的一次運(yùn)行活動(dòng),是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位汉嗽,是操作系統(tǒng)結(jié)構(gòu)的基礎(chǔ)欲逃。在早期面向進(jìn)程設(shè)計(jì)的計(jì)算機(jī)結(jié)構(gòu)中,進(jìn)程是程序的基本執(zhí)行實(shí)體饼暑;在當(dāng)代面向線程設(shè)計(jì)的計(jì)算機(jī)結(jié)構(gòu)中稳析,進(jìn)程是線程的容器。程序是指令撵孤、數(shù)據(jù)及其組織形式的描述迈着,進(jìn)程是程序的實(shí)體

狹義定義:進(jìn)程是正在運(yùn)行的程序的實(shí)例(an instance of a computer program that is being executed)

廣義定義:進(jìn)程是一個(gè)具有一定獨(dú)立功能的程序關(guān)于某個(gè)數(shù)據(jù)集合的一次運(yùn)行活動(dòng)。它是操作系統(tǒng)動(dòng)態(tài)執(zhí)行的基本單元邪码,在傳統(tǒng)的操作系統(tǒng)中裕菠,進(jìn)程既是基本的分配單元,也是基本的執(zhí)行單元闭专。
進(jìn)程的概念主要有兩點(diǎn):

第一奴潘,進(jìn)程是一個(gè)實(shí)體旧烧。每一個(gè)進(jìn)程都有它自己的地址空間,一般情況下画髓,包括文本區(qū)域(text region)掘剪、數(shù)據(jù)區(qū)域(data region)和堆棧(stack region)。文本區(qū)域存儲(chǔ)處理器執(zhí)行的代碼奈虾;數(shù)據(jù)區(qū)域存儲(chǔ)變量和進(jìn)程執(zhí)行期間使用的動(dòng)態(tài)分配的內(nèi)存夺谁;堆棧區(qū)域存儲(chǔ)著活動(dòng)過(guò)程調(diào)用的指令和本地變量。

第二肉微,進(jìn)程是一個(gè)“執(zhí)行中的程序”匾鸥。程序是一個(gè)沒(méi)有生命的實(shí)體,只有處理器賦予程序生命時(shí)(操作系統(tǒng)執(zhí)行之)碉纳,它才能成為一個(gè)活動(dòng)的實(shí)體勿负,我們稱其為進(jìn)程。

進(jìn)程是操作系統(tǒng)中最基本劳曹、重要的概念奴愉。是多道程序系統(tǒng)出現(xiàn)后,為了刻畫系統(tǒng)內(nèi)部出現(xiàn)的動(dòng)態(tài)情況铁孵,描述系統(tǒng)內(nèi)部各道程序的活動(dòng)規(guī)律引進(jìn)的一個(gè)概念,所有多道程序設(shè)計(jì)操作系統(tǒng)都建立在進(jìn)程的基礎(chǔ)上
——摘自百度百科

有一篇非常好锭硼,通俗易懂的博客:進(jìn)程與線程的一個(gè)簡(jiǎn)單解釋

多任務(wù)操作系統(tǒng)可以通過(guò)周期性地將CPU從一個(gè)進(jìn)程切換到另一個(gè)進(jìn)程,來(lái)實(shí)現(xiàn)同時(shí)運(yùn)行多個(gè)進(jìn)程蜕劝,但這導(dǎo)致每個(gè)進(jìn)程看起來(lái)在運(yùn)行期間總是歇歇停停账忘。操作系統(tǒng)通常會(huì)將進(jìn)程相互隔離開,彼此不會(huì)干涉

Java所使用的并發(fā)系統(tǒng)會(huì) 共享 諸如內(nèi)存I/O這樣的資源熙宇,編寫多線程的困難在于協(xié)調(diào)并控制不同線程驅(qū)動(dòng)的任務(wù)之間對(duì)與共享資源的使用,避免多個(gè)線程同時(shí)對(duì)一個(gè)資源進(jìn)行操作

Java的線程機(jī)制是 搶占式 的溉浙。調(diào)度機(jī)制會(huì)周期性地中斷某一正在運(yùn)行的線程烫止,將上下文(任務(wù))切換到另一個(gè)線程,為每個(gè)線程都提供時(shí)間片戳稽,這樣每一個(gè)線程都有機(jī)會(huì)分得一定的時(shí)間來(lái)執(zhí)行任務(wù)馆蠕。


2. 基本的線程機(jī)制

之前對(duì)線程有學(xué)習(xí):Java——Thread線程基礎(chǔ)知識(shí)學(xué)習(xí)

并發(fā)編程將程序分為多個(gè)分離的、獨(dú)立運(yùn)行的任務(wù)惊奇。通過(guò)多線程機(jī)制互躬,每一個(gè)被分離的獨(dú)立任務(wù)(子任務(wù))都會(huì)有一個(gè) 執(zhí)行線程 來(lái)執(zhí)行。一個(gè)線程就是在進(jìn)程中的一個(gè)單一的順序控制流颂郎,一個(gè)進(jìn)程可以擁有多個(gè)并發(fā)執(zhí)行的任務(wù)

在使用多線程時(shí)吼渡,CPU將時(shí)間劃分成片段分配給所有的任務(wù),每個(gè)任務(wù)都會(huì)分配到時(shí)間片


2.1 Runnable定義任務(wù)

線程可以執(zhí)行任務(wù)乓序,任務(wù)則可以用Runnable來(lái)定義寺酪。實(shí)現(xiàn)Runnable接口坎背,重寫run()方法。run()方法則可以理解為具體執(zhí)行任務(wù)的命令寄雀,任務(wù)的具體執(zhí)行過(guò)程

定義LiftOff任務(wù):

public class LiftOff implements Runnable {
    private int countDown = 10;
    private static int taskCount = 0;
    private final int id = taskCount ++;//一旦初始化后得滤,不再改變,加final
    
    public LiftOff(){}

    public LiftOff(int countDown){
       this.countDown = countDown;
    }
    
    @Override
    public void run() {
       while (countDown -- > 0){
           System.out.println(status());
           Thread.yield();//聲明任務(wù)完成盒犹,此刻切換其他線程
       }
    }

    private String status(){
        return "#" +id + " ---> " + (countDown > 0 ? countDown : "LiftOff!");
    }
}

通常run()內(nèi)的命令是一個(gè)循環(huán)懂更,這樣可以確保任務(wù)一直被執(zhí)行下去直到不再需要。根據(jù)任務(wù)需求急膀,循環(huán)需要設(shè)置一些條件來(lái)跳出循環(huán)

Thread.yield() 是向線程調(diào)度器發(fā)出一個(gè)建議沮协,聲明:

我已經(jīng)走完生命中最重要的部分,此刻正是切換其他任務(wù)執(zhí)行的大好時(shí)機(jī)

這樣做的目的是為了能夠產(chǎn)生有趣的輸出脖阵,很可能會(huì)看到任務(wù)換進(jìn)換出的證據(jù)

執(zhí)行任務(wù):

public class MainThread {
    public static void main(String[] args) {
        LiftOff liftOff = new LiftOff();
        liftOff.run();
    }
}

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

#0 ---> 9
#0 ---> 8
#0 ---> 7
#0 ---> 6
#0 ---> 5
#0 ---> 4
#0 ---> 3
#0 ---> 2
#0 ---> 1
#0 ---> LiftOff!

main()方法中皂股,liftOff任務(wù)并沒(méi)有運(yùn)行在一個(gè)單獨(dú)的線程中,而是在main線程中

Runnable命黔,run()方法并不會(huì)產(chǎn)生任何的線程能力呜呐,要實(shí)現(xiàn)線程的行為,Runnable必須顯式地依附在一個(gè)線程之上


2.2 Thread類

通常把一個(gè)Runnable對(duì)象直接通過(guò)Thread的構(gòu)造方法轉(zhuǎn)換為一個(gè)工作任務(wù)

public class BasicThreads {
    public static void main(String[] args) {
        LiftOff liftOff = new LiftOff();
        Thread thread = new Thread(liftOff);
        thread.start();
        for(int i = 0 ; i < 10 ; i ++){
            System.out.println("Waiting for LiftOff");
        }

    }
}

其中的一種運(yùn)行結(jié)果:

Waiting for LiftOff
Waiting for LiftOff
#0 ---> 9
Waiting for LiftOff
Waiting for LiftOff
Waiting for LiftOff
Waiting for LiftOff
#0 ---> 8
Waiting for LiftOff
Waiting for LiftOff
Waiting for LiftOff
Waiting for LiftOff
#0 ---> 7
#0 ---> 6
#0 ---> 5
#0 ---> 4
#0 ---> 3
#0 ---> 2
#0 ---> 1
#0 ---> LiftOff!

thread調(diào)用了start()方法后悍募,新的線程在內(nèi)部會(huì)調(diào)用Runnbalerun()方法蘑辑。但LiftOff是在新的線程執(zhí)行,此時(shí)main()線程仍然可以執(zhí)行其他的操作

當(dāng)main()創(chuàng)建Thread對(duì)象t時(shí)坠宴,并沒(méi)有捕獲t的任何引用洋魂,但每個(gè)Thread對(duì)象注冊(cè)了自己,確實(shí)存在一個(gè)t的引用喜鼓,在任務(wù)完成run()方法結(jié)束副砍,t死亡之前,回收器都無(wú)法回收


更多線程:

public class MoreBasicThreads {
    public static void main(String[] args) {

        for (int i = 0; i < 5; i++) {
            LiftOff liftOff = new LiftOff(2);
            Thread thread = new Thread(liftOff);
            thread.start();
        }
        System.out.println("Waiting for LiftOff");
    }
}

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

Waiting for LiftOff
#0 ---> 1, #1 ---> 1, #0 ---> LiftOff!, #2 ---> 1, #3 ---> 1,#4 ---> 1,
#1 ---> LiftOff!, #2 ---> LiftOff!, #3 ---> LiftOff!, #4 ---> LiftOff!, 

為了方便以后看庄岖,把運(yùn)行結(jié)果做了調(diào)整

運(yùn)行結(jié)果表明了不同任務(wù)執(zhí)行時(shí)豁翎,換進(jìn)換出混在了一起

結(jié)論:一個(gè)Thread對(duì)象創(chuàng)建出一個(gè)單獨(dú)的執(zhí)行線程,調(diào)用了start()方法后隅忿,Thread的對(duì)象仍舊會(huì)存在心剥,直到內(nèi)部的任務(wù)run()方法執(zhí)行完畢,Thread的對(duì)象才會(huì)被回收


2.3 Executor 執(zhí)行器

學(xué)習(xí)資料:

Executor 執(zhí)行器优烧,用于管理Thread對(duì)象,在客戶端和任務(wù)執(zhí)行之間提供了一個(gè)間接層链峭。Excutor允許管理異步任務(wù)的執(zhí)行畦娄,而無(wú)須顯式地管理線程的生命周期

最簡(jiǎn)單的使用:

public class CachedThreadPool {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0 ; i < 3; i ++  ){
             executorService.execute(new LiftOff(3));
        }
        executorService.shutdown();//關(guān)閉線程池
    }

}

結(jié)果:

#1 ---> 2, #0 ---> 2, #2 ---> 2, #1 ---> 1, #0 ---> 1, #2 ---> 1, 
#1 ---> LiftOff!, #0 ---> LiftOff!, #2 ---> LiftOff!, 

Executor是一個(gè)接口,ExectorService接口繼承之Executor,提供了關(guān)閉自己的方法纷责,以及可為跟蹤一個(gè)或多個(gè)異步任務(wù)執(zhí)行狀況而生成 Future 的方法

本例中使用的Executors.newCachedThreadPool()來(lái)生成ExectorService

ExectorService有3個(gè)生命周期狀態(tài): 運(yùn)行捍掺,關(guān)閉,終止

executorService.shutdown()關(guān)閉線程池服務(wù)再膳,便處于關(guān)閉狀態(tài)挺勿,可以防止新任務(wù)再被提交到Executor。當(dāng)前線程(本例中就是main()方法的線程)會(huì)繼續(xù)運(yùn)行shutdown()前提交的任務(wù)喂柒。這個(gè)程序?qū)?huì)在Exector中所有的任務(wù)完成之后盡快退出


Executors提供了一系列的實(shí)用的工廠方法用來(lái)創(chuàng)建線程池

newCachedThreadPool()方法源碼:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}

四種線程池創(chuàng)建方法:

  • newCachedThreadPool
    可緩存的線程池不瓶。沒(méi)有固定大小,如果線程池中的線程數(shù)量超過(guò)任務(wù)執(zhí)行的數(shù)量灾杰,會(huì)回收60秒不執(zhí)行的任務(wù)的空閑線程蚊丐。當(dāng)任務(wù)數(shù)量增加時(shí),線程池自己會(huì)增加線程來(lái)執(zhí)行任務(wù)艳吠。而能創(chuàng)建多少麦备,就得看jvm能夠創(chuàng)建多少

  • newFixedThreadPool
    固定線程數(shù)量大小的線程池,并發(fā)線程數(shù)量不會(huì)超過(guò)固定大小,超出的線程會(huì)在隊(duì)列中等待昭娩。如果一個(gè)正在執(zhí)行的線程出現(xiàn)異常結(jié)束凛篙,會(huì)創(chuàng)建一個(gè)顯得線程來(lái)代替它

  • newScheduledThreadPool
    也是固定線程數(shù)量大小的線程池,可以延遲或者定時(shí)周期執(zhí)行任務(wù)

  • newSingleThreadExecutor
    單例線程池栏渺。線程池中只有一個(gè)線程工作呛梆,出現(xiàn)異常會(huì)有有個(gè)新的線程來(lái)代替厂汗。線程池會(huì)保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級(jí))執(zhí)行鼠冕。

在任何一個(gè)線程池中联予,現(xiàn)有的線程都有可能會(huì)被自動(dòng)復(fù)用

首先考慮使用newCachedThreadPool 雾消,CachedTheadPool在程序執(zhí)行過(guò)程中通常會(huì)創(chuàng)建與所需數(shù)量相同的線程,然后在它回收舊線程時(shí)停止創(chuàng)建新線程

四種類型的線程池都是通過(guò)ThreadFactory接口來(lái)創(chuàng)建的線程


2.3.1 ThreadPoolExecutor

自定義線程池喝滞,學(xué)習(xí)了解參數(shù):

public class ThreadPools {
    public static void main(String[] args) {
        //創(chuàng)建等待隊(duì)列
        BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(10);
        //創(chuàng)建線程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                2,//核心線程數(shù),包含空閑線程
                5,//最大線程數(shù)
                50,//當(dāng)線程數(shù)大于最大線程數(shù)時(shí)异旧,空閑線程等待新任務(wù)的最大時(shí)間
                TimeUnit.MILLISECONDS,//前一個(gè)參數(shù)涵卵,等待時(shí)間的單位
                blockingQueue//任務(wù)執(zhí)行前保存任務(wù)的隊(duì)列莱褒,僅保存由execute方法提交的Runnable任務(wù)
        );
        for (int i = 0; i < 5; i++) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " ---> 正在執(zhí)行");
                }
            });

//          threadPoolExecutor.execute(() -> System.out.println(Thread.currentThread().getName() + " ---> 正在執(zhí)行"));

            //關(guān)閉線程池
            threadPoolExecutor.shutdown();
        }

    }
}

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

pool-1-thread-1 ---> 正在執(zhí)行
pool-1-thread-2 ---> 正在執(zhí)行
pool-1-thread-1 ---> 正在執(zhí)行
pool-1-thread-2 ---> 正在執(zhí)行
pool-1-thread-1 ---> 正在執(zhí)行

源碼:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
}

根據(jù)ThreadPoolExecutor源碼前面大段的注釋雁刷,我們可以看出,當(dāng)試圖通過(guò)excute方法講一個(gè)Runnable任務(wù)添加到線程池中時(shí)保礼,按照如下順序來(lái)處理:

  1. 如果線程池中的線程數(shù)量少于corePoolSize,即使線程池中有空閑線程责语,也會(huì)創(chuàng)建一個(gè)新的線程來(lái)執(zhí)行新添加的任務(wù)

  2. 如果線程池中的線程數(shù)量大于等于corePoolSize炮障,但緩沖隊(duì)列workQueue未滿,則將新添加的任務(wù)放到workQueue中坤候,按照FIFO的原則依次等待執(zhí)行(線程池中有線程空閑出來(lái)后依次將緩沖隊(duì)列中的任務(wù)交付給空閑的線程執(zhí)行)

  3. 如果線程池中的線程數(shù)量大于等于corePoolSize胁赢,且緩沖隊(duì)列workQueue已滿,但線程池中的線程數(shù)量小于maximumPoolSize白筹,則會(huì)創(chuàng)建新的線程來(lái)處理被添加的任務(wù)

  4. 如果線程池中的線程數(shù)量等于了maximumPoolSize智末,有4種處理方式(該構(gòu)造方法調(diào)用了含有5個(gè)參數(shù)的構(gòu)造方法谅摄,并將最后一個(gè)構(gòu)造方法為RejectedExecutionHandler類型,它在處理線程溢出時(shí)有4種方式)

總結(jié)起來(lái)系馆,也即是說(shuō)送漠,當(dāng)有新的任務(wù)要處理時(shí),先看線程池中的線程數(shù)量是否大于corePoolSize由蘑,再看緩沖隊(duì)列workQueue是否滿闽寡,最后看線程池中的線程數(shù)量是否大于maximumPoolSize

另外,當(dāng)線程池中的線程數(shù)量大于corePoolSize時(shí)尼酿,如果里面有線程的空閑時(shí)間超過(guò)了keepAliveTime爷狈,就將其移除線程池,這樣裳擎,可以動(dòng)態(tài)地調(diào)整線程池中線程的數(shù)量涎永。

以上這段,摘自【Java并發(fā)編程】之十九:并發(fā)新特性—Executor框架與線程池


2.4 Callable,從任務(wù)產(chǎn)生返回值

TaskWithResult代碼:

public class TaskWithResult implements Callable<String> {

    private int id;

    public TaskWithResult(int id) {
        this.id = id;
    }

    @Override
    public String call() throws Exception {
        return "TaskWithResult ---> " + id;
    }
}

實(shí)現(xiàn)Callable<>接口鹿响,重寫call()方法羡微。call()的返回值就是異步線程所想要返回的任務(wù)結(jié)果

CallableDemo代碼:

public class CallableDemo {
    public static void main(String[]args){
        test();
    }


    private static void test() {
        //創(chuàng)建線程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //Future集合
        ArrayList<Future<String>> list = new ArrayList<>();
        for (int i = 0; i < 5;i++){
            Future<String> future = executorService.submit(new TaskWithResult(i));
            list.add(future);
        }
        //遍歷
        for (Future<String> future : list){
            try {
                    System.out.println(future.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            } finally {
                //記得關(guān)閉線程池
                executorService.shutdown();
            }
        }
    }    }
}

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

TaskWithResult ---> 0
TaskWithResult ---> 1
TaskWithResult ---> 2
TaskWithResult ---> 3
TaskWithResult ---> 4

提交任務(wù)用的是:executorService.submit()

submit()方法會(huì)產(chǎn)生Future對(duì)象,并用Callble的返回結(jié)果作為泛型抢野】教裕可以使用Future對(duì)象的isDone()來(lái)查看Future是否完成,當(dāng)任務(wù)完成時(shí)指孤,會(huì)具有一個(gè)結(jié)果启涯,可以使用get()方法來(lái)獲取結(jié)果

不用isDone()判斷,而直接使用get()方法來(lái)獲取結(jié)果時(shí)恃轩,get()會(huì)阻塞任務(wù)線程结洼,直到能夠獲取結(jié)果。get()方法又一個(gè)具有設(shè)置超時(shí)的重載方法


submit() 和 execute() 直觀區(qū)別:

  • execute(Runnable command)
  • <T> Future<T> submit(Callable<T> task),Future<?> submit(Runnable task)
  1. 參數(shù)不同叉跛,execute()參數(shù)只能為Runnable,而submit()支持兩種類型
  2. submit()具有Future的類型返回值
  3. execute()Executor的方法松忍,submit()ExecutorService的方法,ExecutorExecutorService的父接口

2.4.1 Future未來(lái)

單詞的意思是: 未來(lái)筷厘,將來(lái)時(shí)

源碼中的注釋說(shuō)明:

A {@code Future} represents the result of an asynchronous
computation.  Methods are provided to check if the computation is
complete, to wait for its completion, and to retrieve the result of
the computation.  The result can only be retrieved using method
{@code get} when the computation has completed, blocking if
necessary until it is ready.  Cancellation is performed by the
{@code cancel} method.  Additional methods are provided to
determine if the task completed normally or was cancelled. Once a
computation has completed, the computation cannot be cancelled.
If you would like to use a {@code Future} for the sake
of cancellability but not provide a usable result, you can
declare types of the form {@code Future<?>} and
return {@code null} as a result of the underlying task.

個(gè)人理解:

一個(gè)異步任務(wù)結(jié)果容器鸣峭。并具有對(duì)任務(wù)結(jié)果操作的能力∷盅蓿可以查看結(jié)果是否完成摊溶,取消結(jié)果,獲取結(jié)果

  • boolean isDone()

    如果任務(wù)結(jié)束充石,任務(wù)正常結(jié)束或者任務(wù)被取消了莫换,就返回true

  • boolean isCancelled()

    任務(wù)在完成返回結(jié)果之前被取消,會(huì)返回true

  • boolean cancel(boolean mayInterruptIfRunning)

    取消正在執(zhí)行的任務(wù)。如果要取消的任務(wù)目標(biāo)已經(jīng)完成拉岁,或者已經(jīng)被取消過(guò)坷剧,或者由于其他原因無(wú)法取消,取消操作便會(huì)失敗喊暖,返回false惫企。如果一個(gè)任務(wù)在執(zhí)行前進(jìn)行了取消操作,這個(gè)任務(wù)便再不會(huì)被執(zhí)行哄啄。如果任務(wù)線程已經(jīng)開始雅任,mayInterruptIfRunning將決定是否中斷任務(wù),true就會(huì)將正在執(zhí)行的任務(wù)取消咨跌。false時(shí)沪么,已經(jīng)開始的任務(wù)便可以繼續(xù)執(zhí)行

    返回值為false時(shí),代表取消失敗锌半,任務(wù)已經(jīng)完成禽车,結(jié)果已經(jīng)返回;返回值為true時(shí)刊殉,表示任務(wù)取消殉摔,返回的Future容器中并沒(méi)有任何結(jié)果

    調(diào)用這個(gè)方法后有了返回值之后,被取消的任務(wù)返回的Future對(duì)象记焊,調(diào)用isDone()方法便會(huì)返回true逸月;isCancel()方法的返回值則由cancel()的值來(lái)決定

  • V get() throws InterruptedException, ExecutionException

    必要的時(shí)候會(huì)發(fā)生阻塞,直到任務(wù)完成遍膜,返回結(jié)果

* @return the computed result
* @throws CancellationException if the computation was cancelled
* 返回任務(wù)結(jié)果時(shí)碗硬,被取消了
* @throws ExecutionException if the computation threw an exception
* 任務(wù)結(jié)果自身發(fā)生異常
* @throws InterruptedException if the current thread was interrupted while waiting
* 在阻塞等待結(jié)果時(shí),任務(wù)線程被中斷

這個(gè)方法拋出3個(gè)異常


2.5 休眠與讓步

代碼:

public class SleepingTask extends LiftOff{
    @Override
    public void run() {
        super.run();
        while (countDown-- > 0){
            System.out.println(status());
            //1.5前
            // Thread.sleep(100);
            //1.5后
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0 ; i < 3; i ++){
            executorService.execute(new SleepingTask());
        }
        executorService.shutdown();
    }
}

Java SE5中添加了更加顯示的sleep()方法版本瓢颅,直接通過(guò)TimeUtil來(lái)指定休眠的時(shí)間單位恩尾,sleep(long timeout)調(diào)用會(huì)線程處于阻塞狀態(tài)

yield()方法是讓步,調(diào)用后會(huì)給線程調(diào)度器一個(gè)暗示挽懦,可以讓別的線程來(lái)使用cpu翰意。但也僅僅是個(gè)暗示,沒(méi)有辦法保證這個(gè)暗示一定會(huì)被采納信柿,只是建議讓擁有相同優(yōu)先級(jí)的其他線程運(yùn)行


2.6 優(yōu)先級(jí)

線程優(yōu)先級(jí)的作用就是將線程的重要性告訴調(diào)度器冀偶。自然而然,優(yōu)先級(jí)高的線程就越是有機(jī)會(huì)優(yōu)先執(zhí)行渔嚷。優(yōu)先級(jí)低并不意味著得不到執(zhí)行进鸠,優(yōu)先權(quán)并不會(huì)導(dǎo)致死鎖。

簡(jiǎn)單Dome:

public class SimplePriorities implements Runnable {
    private int countDown = 2;
    private volatile double d;
    private int priority;

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

    @Override
    public String toString() {
        return Thread.currentThread() + " --> " + countDown;
    }

    @Override
    public void run() {
        Thread.currentThread().setPriority(priority);
        while (true) {
            for (int i = 1; i < 100000; i++) {
                d += (Math.PI + Math.E) / (double) i;
                if (i % 1000 == 0) {
                    Thread.yield();
                }
            }
            System.out.println(this);
            if (--countDown == 0) return;
        }
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 3; i++) {
           executorService.execute(new SimplePriorities(Thread.MIN_PRIORITY)); //最低優(yōu)先級(jí)
        }
        executorService.execute(new SimplePriorities(Thread.MAX_PRIORITY));//最高優(yōu)先級(jí)
        executorService.shutdown();
    }

}

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

Thread[pool-1-thread-1,1,main] --> 2
Thread[pool-1-thread-2,1,main] --> 2
Thread[pool-1-thread-4,10,main] --> 2
Thread[pool-1-thread-3,1,main] --> 2
Thread[pool-1-thread-2,1,main] --> 1
Thread[pool-1-thread-1,1,main] --> 1
Thread[pool-1-thread-4,10,main] --> 1
Thread[pool-1-thread-3,1,main] --> 1

main()方法中圃伶,前3個(gè)線程的優(yōu)先級(jí)為最低,最后一個(gè)線程為最高

注意:盡量不要構(gòu)造方法中,設(shè)置優(yōu)先級(jí)窒朋。上面的代碼是在run()方法開頭進(jìn)行設(shè)置搀罢,此時(shí)Executor此時(shí)還沒(méi)開始執(zhí)行任務(wù)

run()方法中執(zhí)行了100000次開銷比較大的浮點(diǎn)運(yùn)算。其中變量dvolatile類型的侥猩,以確保不進(jìn)行任何的變量?jī)?yōu)化

書上作者說(shuō)加浮點(diǎn)運(yùn)算是為了更好的看到優(yōu)先級(jí)高的線程先運(yùn)行榔至,作者在Winodws XP 下優(yōu)先級(jí)最高的線程任務(wù)全部運(yùn)行完,低優(yōu)先級(jí)的才運(yùn)行欺劳,而我測(cè)試的不是

volatile讓變量每次在使用的時(shí)候唧取,都從主存中取。而不是從各個(gè)線程的“工作內(nèi)存”划提。

volatile具有synchronized關(guān)鍵字的“可見性”枫弟,但是沒(méi)有synchronized關(guān)鍵字的“并發(fā)正確性”,也就是說(shuō)不保證線程執(zhí)行的有序性鹏往。

也就是說(shuō)淡诗,volatile變量對(duì)于每次使用,線程都能得到當(dāng)前volatile變量的最新值伊履。但是volatile變量并不保證并發(fā)的正確性韩容。

摘自Java中volatile的作用以及用法
volatile這個(gè)修飾符沒(méi)有用過(guò),查了下唐瀑,暫時(shí)不理解

JDK中群凶,線程共有10個(gè)優(yōu)先級(jí),默認(rèn)為 NORM_PRIORITY = 5 ,但與各個(gè)操作系統(tǒng)映射得不是很好哄辣。在Winodws中有7個(gè)優(yōu)先級(jí)并且不是固定的请梢,所有優(yōu)先級(jí)的映射關(guān)系也不是很準(zhǔn)。具有可移植性的是: MAX_PRIORITY 柔滔, NORM_PRIORITY 溢陪, MIN_PRIORITY


2.7 后臺(tái)線程

后臺(tái)(daemon)線程:在程序運(yùn)行的時(shí)候在后臺(tái)提供了一種通用服務(wù)的線程,并且這種線程不屬于程序中不可或缺的部分睛廊。

當(dāng)所有的非后臺(tái)線程運(yùn)行結(jié)束了形真,程序也就終止了,同時(shí)還會(huì)殺死所有的后臺(tái)線程超全。反過(guò)來(lái)說(shuō)咆霜,只要任何非后臺(tái)線程還在運(yùn)行,程序就不會(huì)終止

簡(jiǎn)單案例:

public class SimpleDaemons implements Runnable{
    @Override
    public void run() {
        while(true){
            try {
                TimeUnit.MILLISECONDS.sleep(100);
                System.out.println(Thread.currentThread() +"  "+this);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        for (int i = 0 ; i < 5;i ++){
            Thread daemon = new Thread(new SimpleDaemons());
            daemon.setDaemon(true);
            
            daemon.start();
        }
        System.out.println("后臺(tái)線程已全部啟動(dòng)");
        try {
            TimeUnit.MILLISECONDS.sleep(175);//可修改之來(lái)觀察打印結(jié)果
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

關(guān)鍵也就一句 daemon.setDaemon(true)

可以通過(guò)isDaemon()來(lái)判斷線程是否為一個(gè)后臺(tái)線程嘶朱。若為一個(gè)后臺(tái)線程蛾坯,那它創(chuàng)建的任何線程都將被自動(dòng)設(shè)置為后后臺(tái)線程

后臺(tái)線程沒(méi)有使用過(guò),也不知道啥場(chǎng)景適合使用疏遏。脉课。救军。


2.8 加入一個(gè)線程

一個(gè)線程t在自己的run()內(nèi)執(zhí)行另外一個(gè)線程ntnt.join()方法,t線程便會(huì)被掛起倘零,nt線程結(jié)束后唱遭,也就是nt.isAlive()false,第一個(gè)線程t才會(huì)繼續(xù)執(zhí)行

可以在調(diào)用join()方法時(shí)帶上一個(gè)超時(shí)參數(shù)呈驶,單位可以為毫秒拷泽,納秒,當(dāng)目標(biāo)線程nt在設(shè)置的超時(shí)時(shí)間到期后還沒(méi)有結(jié)束袖瞻,join()方法便會(huì)返回司致,t也就不再會(huì)執(zhí)行

簡(jiǎn)單案例:

public class Joining {
    public static void main(String[] args) {
        OtherThread otherThread = new OtherThread("OtherThread",1500);
        OneThread oneThread = new OneThread("OneThread",otherThread);
//        System.out.println("--------");
//        OtherThread interrupt = new OtherThread("InterruptThread",1500);
//        OneThread oneThread_2 = new OneThread("OneThread_2",interrupt);
//        interrupt.interrupt();

    }
}

class OtherThread extends Thread {
    private int duration;

    public OtherThread(String name, int sleepTime) {
        super(name);
        this.duration = sleepTime;
        start();
    }

    @Override
    public void run() {
        try {
            sleep(duration);
        } catch (InterruptedException e) {
            System.out.println(getName() + " was interrupted, " +
                               " isInterrupted(): "+isInterrupted());
            return;
        }
        System.out.println(getName() + " --> has awakened");
    }
}

class OneThread extends Thread{
    private OtherThread otherThread;

    public OneThread(String name,OtherThread otherThread) {
        super(name);
        this.otherThread = otherThread;
        start();
    }

    @Override
    public void run() {
        try {
            otherThread.join();//把otherThread線程加入進(jìn)來(lái)
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName()+" --> OneThread complete");
    }
}

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

OtherThread --> has awakened and complete
OneThread --> OneThread complete

個(gè)人理解:
個(gè)人感覺(jué)join()方法就是加塞,具有優(yōu)先執(zhí)行的特權(quán)聋迎,誰(shuí)調(diào)用了join()脂矫,誰(shuí)就有了特權(quán),可以優(yōu)先執(zhí)行砌庄。有特權(quán)的線程執(zhí)行完成后羹唠,沒(méi)特權(quán)的線程才開始執(zhí)行

CyclicBarrier工具類比join()方法更適合用來(lái)加入一個(gè)線程


3. 最后

這一章知識(shí)點(diǎn)好多,這才是娄昆,而且感覺(jué)想要理解都需要花費(fèi)不少時(shí)間佩微,感覺(jué)能吸收一半就不錯(cuò)了,以后得多看書萌焰,多看博客

本人很菜哺眯,有錯(cuò)誤請(qǐng)指出

共勉 :)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市扒俯,隨后出現(xiàn)的幾起案子奶卓,更是在濱河造成了極大的恐慌,老刑警劉巖撼玄,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夺姑,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡掌猛,警方通過(guò)查閱死者的電腦和手機(jī)盏浙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)荔茬,“玉大人废膘,你說(shuō)我怎么就攤上這事∧轿担” “怎么了丐黄?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)孔飒。 經(jīng)常有香客問(wèn)我灌闺,道長(zhǎng)艰争,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任桂对,我火速辦了婚禮园细,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘接校。我一直安慰自己,他們只是感情好狮崩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布蛛勉。 她就那樣靜靜地躺著,像睡著了一般睦柴。 火紅的嫁衣襯著肌膚如雪诽凌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天坦敌,我揣著相機(jī)與錄音侣诵,去河邊找鬼。 笑死狱窘,一個(gè)胖子當(dāng)著我的面吹牛杜顺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蘸炸,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼躬络,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了搭儒?” 一聲冷哼從身側(cè)響起穷当,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎淹禾,沒(méi)想到半個(gè)月后馁菜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡铃岔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年汪疮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片德撬。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡铲咨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蜓洪,到底是詐尸還是另有隱情纤勒,我是刑警寧澤,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布隆檀,位于F島的核電站摇天,受9級(jí)特大地震影響粹湃,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泉坐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一为鳄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧腕让,春花似錦孤钦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至觉鼻,卻和暖如春俊扭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坠陈。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工萨惑, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人仇矾。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓庸蔼,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親贮匕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子朱嘴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

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