我所知道併發編程之使用定時器、線程池方式演示多種線程建立(二)

上一篇文章介紹了Runable、匿名類、帶返回值的線程建立方式數據庫

接下來咱們這篇要介紹使用定時器、線程池、Lambda表達式等方式來建立編程

1、使用定時器建立線程


定時器其實也是至關於開闢一個線程來進行執行定時任務,就是咱們所熟悉的Timer類緩存

好比說咱們想在某一個時間點上執行一件事,好比凌晨要跑數據,或者實現20分鐘以後提示咱們一些事等等,均可以經過定時器來執行多線程

定時器關於定時任務,除了JDK所給咱們提供的Timer類這個API之外,還有不少的第三方的關於定時的任務的框架。併發

好比Spring就對定時任務進行很是好的支持,還有一個很是強大的關於計劃任務的框架叫quartz,quartz也是說的企業中咱們作定時任務的專門的一個系統框架

下面咱們看一下定時器的實現,看看怎麼實現一個定時的任務ide

class Demo4{

    public static void main(String[] args) {

        Timer task = new Timer();

    }
}

咱們能夠經過schedule()這個方法來提交一個定時任務,定時任務就是TimerTask task,後面那個參數就是你能夠經過指定延遲多長時間執行等等學習

class Demo4{


    public static void main(String[] args) {

        Timer timer = new Timer();
        
        /* time爲Date類型,在指定時間執行一次*/
        timer.schedule(TimerTask task, Date time)

        /* firstTime爲Date類型,period爲long,
         在firstTime時刻第一次執行,以後每隔period毫秒執行一次*/
        timer.schedule(TimerTask task, Date firstTime, long period)

        /* delay爲long類型,從當前開始delay毫秒後執行一次*/
        timer.schedule(TimerTask task, long delay)

        /* delay爲long類型,period爲long,
         從當前開始delay毫秒後執行一次,以後每隔period毫秒執行一次*/
        timer.schedule(TimerTask task, long delay, long period)
    }
}

咱們想讓它馬上執行而後每隔1秒執行一次,那麼咱們就可使用這個構造方法spa

image.png

第一個參數線程任務TimerTask是一個抽象類,一塊兒看看他的源碼是怎麼樣的線程

public abstract class TimerTask implements Runnable {
    
    //省略其餘關鍵性代碼......
}

咱們發現它也實現了Runnable接口,因此咱們能夠在run()方法裏面就能夠實現定時任務

class Demo4{

    public static void main(String[] args) {

        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("timeTask start.....");
            }
        },0,1000);
    }
}
//運行結果以下:
timeTask start.....
timeTask start.....
timeTask start.....
timeTask start.....
timeTask start.....
timeTask start.....

咱們設置延遲0,設置每隔1s執行一次完成進行輸出

這種寫法咱們發現它有一個很是大的問題就是不可控,控制起來很是麻煩。

並且當這個任務沒有執行完畢或者咱們想每次都提交不一樣的任務的話,那麼咱們無法對它進行持久化等操做

定時器咱們就說到這裏

2、使用線程池建立線程


在JDK中給咱們提供線程池,方便咱們使用線程池可以建立一個多線程

什麼是線程池?就是它首先是一個池子,這個池子裏面裝的是線程也就是說一個池子裏面裝了不少的線程

當咱們在去使用線程的時候,而不須要再去建立線程了而是直接從池子裏面去獲取線程

當咱們用完線程的時候也不去釋放它,而是還給所謂的線程池,這就跟數據庫鏈接池的原理是同樣的它主要是下降線程的建立和銷燬的資源的浪費,就至關於拿空間換時間,就是一個典型的緩存

首先咱們先介紹一下線程池最上層的接口:Executor

image.png

咱們發現它下面有不少的實現,通常用的最多的其實就是ExecutorService

image.png

剛剛提到Executor就表明一個線程池,但咱們發現Executor是一個接口,如何建立它呢?咱們無法new,不過能夠經過Executors類下的方法建立

image.png

咱們能夠經過這些方法來建立一個線程池,這些都是建立線程池的方法

image.png

同時咱們發現這些方法的返回值要麼是ExecutorService,要麼是ScheduledExecutorService。

image.png

而咱們剛剛也知道通常使用最多的是ExecutorService子接口實現

因此咱們使用父接口去指向子類接口並無什麼問題

//建立一個可緩存線程池若是線程池長度超過處理須要
//可靈活回收空閒線程,若無可回收,則新建線程。
newCachedThreadPool

//建立一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
newFixedThreadPool 

//建立一個定長線程池,支持定時及週期性任務執行。
newScheduledThreadPool 

//建立一個單線程化的線程池,它只會用惟一的工做線程來執行任務
//保證全部任務按照指定順序(FIFO, LIFO, 優先級)執行
newSingleThreadExecutor

咱們使用FixedThreadPool來建立一個固定容量的線程池,關於其餘的線程池後面來詳細的學習

class Demo5{

    public static void main(String[] args) {

        //建立十個線程
        Executor executor = Executors.newFixedThreadPool(10);
        
    }
}

當咱們用到線程的時候,就向它(本例中threadPool)申請,用完了以後在返回給它

class Demo5{

    public static void main(String[] args) {

        //建立十個線程
        Executor executor = Executors.newFixedThreadPool(10);
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"start....");
            }
        });
    }
}
//運行結果以下:
pool-1-thread-1start....

這就是咱們的線程任務只執行了一次,每一次execute()提交的是一個線程任務。

咱們說線程池中有多個線程,那麼也就是說能夠同時提交多個線程任務

class Demo5{

    public static void main(String[] args) {

        //建立十個線程
        Executor executor = Executors.newFixedThreadPool(10);

        for (int i = 0; i<10; i++){
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"start....");
                }
            });
        }
    }
}
//運行結果以下:
pool-1-thread-1start....
pool-1-thread-7start....
pool-1-thread-2start....
pool-1-thread-3start....
pool-1-thread-6start....
pool-1-thread-4start....
pool-1-thread-9start....
pool-1-thread-10start....
pool-1-thread-5start....
pool-1-thread-8start....

當你運行起來就會發現當執行完畢以後這個程序並無中止,由於這是一個線程池,而你並無告訴它讓它停掉

class Demo5{

    public static void main(String[] args) {

        //建立十個線程
        ExecutorService executor = Executors.newFixedThreadPool(10);

        for (int i = 0; i<10; i++){
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"start....");
                }
            });
        }
        //中止線程池
        executor.shutdown();
    }
}

假如以目前的代碼提交一百次線程任務會輸出什麼呢?

class Demo5{

    public static void main(String[] args) {

        //建立十個線程
        ExecutorService executor = Executors.newFixedThreadPool(10);

        for (int i = 0; i<100; i++){
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"start....");
                }
            });
        }
        //中止線程池
        executor.shutdown();
    }
}
//運行結果以下:
pool-1-thread-1start....
pool-1-thread-2start....
pool-1-thread-1start....
pool-1-thread-1start....
pool-1-thread-1start....
pool-1-thread-1start....
pool-1-thread-1start....
pool-1-thread-7start....

這時咱們發現當咱們提交一百個定時任務,發現並非由100個線程所執行

也就是說如今至關於什麼呢?第一個線程任務提交進來了,被第一個線程執行了可能,而後接着第二個線程任務被提交了,那麼可能第二個線程就接着來幹活。

哪一個線程搶到執行權就那個線程去幹活,這時有可能上面的尚未乾完,而第二個線程幹活比較利落,它幹完了以後返回給線程池(本例中的線程池是threadPool)

接着咱們又要提交一個線程任務,因而從線程池中拿到第二個線程接着幹活因而就是這麼一個狀況,也就是說你即便提交了100個線程任務,它依然是由這10個線程來進行幹活

接下來咱們看看CachedThreadPool是一個什麼樣的,也是先來提交10個線程任務

image.png
image.png

咱們發現它差很少也是給咱們建立了10個線程,那麼,咱們再來提交100個線程任務

image.png

image.png

咱們發現它的最大值達到了50了,其實這個是沒有規律的

也就是說它這個線程池的大小,它是怎麼來決定的呢?

其實它就是由你線程任務,你不斷的提交線程任務,那麼它就不斷的建立

它認爲不夠用了它就去建立,它認爲夠用了它就回收

這是關於CachedThreadPool,比較智能的一個線程池

參考資料


龍果學院:併發編程原理與實戰(葉子猿老師)

相關文章
相關標籤/搜索