01-併發編程之Executor線程池詳解

目錄

  1. 線程
    1. 線程定義
    2. 多線程定義
    3. 線程實現方式
    4. 線程的生命週期&狀態
    5. 線程如何排查死鎖
    6. 線程的運行順序
  2. 線程池
    1. 線程池和線程對比性能測試
    2. 體系介紹
    3. 普通線程池ThreadPoolExecutor結構
    4. 線程池運行思路
    5. 通過debug瞭解線程池底層
    6. 線程流程圖
    7. 拒絕策略
    8. 簡單線程池監控

一、線程

1. 線程:

線程是進程的一個實體,是 CPU 調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。

2. 多線程:

多線程指在單個程序中可以同時運行多個不同的線程執行不同的任務。多線程編程的目的,就是“最大限度地利用 cpu 資源”,當某一線程的處理不需要佔用 cpu 而只和 io 等資源打交道時,讓需要佔用 Cpu 的其他線程有其他機會獲得 cpu 資源。從根本上說,這就是多線程編程的最終目的。

3. 線程實現的方式:

Runnable、Thread、Callable

#Thread 繼承Thread類 重寫run方法
public class Thread01 extends Thread {
    public static void main(String[] args) {
        new Thread01().start();
    }
    @Override
    public void run() {
        System.out.println("Thread01 start up");
    }
}
#Runnable 實現Runnable接口,在創建線程中傳入
public class Runnable01 implements Runnable {
    public static void main(String[] args) {
        new Thread(new Runnable01()).start();
    }
    @Override
    public void run() {
        System.out.println("Thread start by Runnable01");
    }
}
#Callable
public class Callable01 implements Callable<String> {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask(new Callable01());
        new Thread(futureTask).start();
        System.out.println(futureTask.get());
    }
    @Override
    public String call() throws Exception {
        return "Thread start by Callable";
    }
}

小結

實現 Runnable 接口相比繼承 Thread 類有如下優勢
1)可以避免由於 Java 的單繼承特性而帶來的侷限
2)增強程序的健壯性,代碼能夠被多個線程共享,代碼與數據是獨立的
3)線程池只能放入實現 Runable 或 Callable 類線程,不能直接放入繼承 Thread 的類
實現 Runnable 接口和實現 Callable 接口的區別
1)Runnable 是自從 java1.1 就有了,而 Callable 是 1.5 之後才加上去的
2)實現 Callable 接口的任務線程能返回執行結果,而實現 Runnable 接口的任務線程不能返回結果
3)Callable 接口的 call()方法允許拋出異常,而 Runnable 接口的 run()方法的異常只能在內部消化,不能繼
續上拋
4)加入線程池運行,Runnable 使用 ExecutorService 的 execute 方法,Callable 使用 submit 方法
注:Callable 接口支持返回執行結果,此時需要調用 FutureTask.get()方法實現,此方法會阻塞主線程直到獲
取返回結果,當不調用此方法時,主線程不會阻塞

4. 線程的生命週期&狀態:

在這裏插入圖片描述
在這裏插入圖片描述

5. 線程如何排查死鎖:
  1. 運行以下代碼,模擬環境,命令行 jps查看java進程服務,然後jstack查看對應服務狀態
    在這裏插入圖片描述
public class ThreadState {
    public static void main(String[] args) {
        new Thread(new TimeWaiting(),"TimeWaitingThread").start();
        new Thread(new Waiting(),"WaitingThread").start();
        new Thread(new Blocked(),"BlockedThread-1").start();
        new Thread(new Blocked(),"BlockedThread-2").start();
    }
    static class Waiting implements Runnable{
        @Override
        public void run() {
            while (true){
                synchronized (Waiting.class){
                    try {
                        Waiting.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    static class TimeWaiting implements Runnable{
        @Override
        public void run() {
            while (true)
                second(100);
        }
    }
    static class Blocked implements Runnable{
        @Override
        public void run() {
            synchronized (Blocked.class){
                while (true){
                    second(100);
                }
            }
        }
    }
    public static final void second(long seconds){
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

  1. 或者在java開發工具中,運用快照功能(左側照相機圖樣)
    在這裏插入圖片描述
6. 線程的運行順序:
public class ThreadSort {
	//lumbda表達式 jdk1.8後可以使用
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(()-> {
                System.out.println("thread1");
        });
        Thread thread2 = new Thread(()-> {
            System.out.println("thread2");
        });
        Thread thread3 = new Thread(()-> {
            System.out.println("thread3");
        });
        //1.運行這一塊,你會發現幾次運行中,線程的運行是無序的。因爲通過線程生命週期圖可以發現,其實線程在start後並不是已經開始運行,而是等待內存cpu分配時間分片後,纔是真正開始運行,分配時間分配的資源問題導致了運行順序不一致
//        thread1.start();
//        thread2.start();
//        thread3.start();

		// join可以保證運行順序,點進join() 方法會發現它是一個synchronized方法,所以主線程會首先持有thread線程對象的鎖。接下來在join()方法裏面調用wait()方法,主線程會釋放thread線程對象的鎖,進入等待狀態。最後,threadA線程執行結束,JVM會調用lock.notify_all(thread);喚醒持有threadA這個對象鎖的線程,也就是主線程,所以主線程會繼續往下執行。
		thread1.start();
        thread1.join();
        thread2.start();
        thread2.join();
        thread3.start();
        thread3.join();
    }
}

一、線程池

1.線程池和線程對比性能測試:
#線程池運行
public class ThreadPoolPKTest {
    public static void main(String[] args) throws InterruptedException {
        Long start = System.currentTimeMillis();
        final List<Integer> list = new ArrayList<>();
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        final Random random = new Random();
        for (int i = 0; i < 100000;i++){
            final int j = 1;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    list.add(j);
                }
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(1,TimeUnit.DAYS);
        System.out.println(System.currentTimeMillis() - start);
        System.out.println(list.size());
    }
}
#線程運行
public class ThreadPKTest {
    public static void main(String[] args) throws InterruptedException {
        Long start = System.currentTimeMillis();
        final List<Integer> list = new ArrayList<>();
        final Random random = new Random();
        for (int i = 0; i < 100000; i++){
            final int j = 1;
            Thread thread = new Thread(){
                @Override
                public void run() {
                    list.add(j);
                }
            };
            thread.start();
            thread.join();
        }
        System.out.println(System.currentTimeMillis() - start);
        System.out.println(list.size());
    }
}

2.體系介紹:

在這裏插入圖片描述

層次 名稱 方法 說明 類型
1 java.util.concurrent.Executor java.util.concurrent.Executor#execute 執行接口 接口
2 java.util.concurrent.ExecutorService java.util.concurrent.ExecutorService
#submit(java.util.concurrent.Callable)
提交接口 接口
3 java.util.concurrent.AbstractExecutorService java.util.concurrent.AbstractExecutorService
#submit(java.util.concurrent.Callable)
把執行和提交接口進行合併區別:有返回值和無返回值 抽象類
4 java.util.concurrent.ThreadPoolExecutor java.util.concurrent.ThreadPoolExecutor
#execute
調 用 addwork( offer>task 放隊列)Run 方 法 調 用runwork 方 法getTask(從隊列拿數據) 實現類
5 java.util.concurrent.ScheduledExecutorService Schedule 、scheduleAtFixedRat 、scheduleWithFixedDelay 定義方法、定義接口 接口
6 java.util.concurrent.ScheduledThreadPoolExecutor delayedExecute 具體實現add>task>addWork 實現類
四種線程池

Java通過Executors提供四種線程池,分別爲:

  1. newSingleThreadExecutor
    創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。
  2. newFixedThreadPool
    創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
  3. newScheduledThreadPool
    創建一個可定期或者延時執行任務的定長線程池,支持定時及週期性任務執行。
  4. newCachedThreadPoo
    創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。
3. 普通線程池:java.util.concurrent.ThreadPoolExecutor
內部類結構

在這裏插入圖片描述

五個內布類,分爲兩種類型:

*Policy:策略類型
Worker:工作類型

內部工作原理(構造方法賦值)
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) 

• corePoolSize :池中所保存的線程數,包括空閒線程
• maximumPoolSize:池中允許的最大線程數
• keepAliveTime: 當線程數大於核心時,此爲終止前多餘的空閒線程等待新任務的最長時間
• unit:keepAliveTime 參數的時間單位
• workQueue :執行前用於保持任務的隊列。此隊列僅保持由 execute 方法提交的 Runnable 任務
• threadFactory:執行程序創建新線程時使用的工廠
• handler :由於超出線程範圍和隊列容量而使執行

4.線程池運行思路

如果當前池大小 poolSize 小於 corePoolSize ,則創建新線程執行任務
如果當前池大小 poolSize 大於 corePoolSize ,且等待隊列未滿,則進入等待隊列
如果當前池大小 poolSize 大於 corePoolSize 且小於 maximumPoolSize ,且等待隊列已滿,則創建新線程
執行任務
如果當前池大小 poolSize 大於 corePoolSize 且大於 maximumPoolSize ,且等待隊列已滿,則調用拒絕策
略來處理該任務
線程池裏的每個線程執行完任務後不會立刻退出,而是會去檢查下等待隊列裏是否還有線程任務需要執行,
如果在 keepAliveTime 裏等不到新的任務了,那麼線程就會退出

5.通過debug瞭解線程池底層
public class ThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        /**
        *	Submit 和 execute 方法
		*	1、有返回值和無返回值
		*	2、返回的Task 不一樣,一個是futuretask 一個 task 本身
        */
        /*executorService.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
                System.out.println("hello ThreadPool");
            }
        });*/
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
                System.out.println("hello ThreadPool");
            }
        });
        executorService.shutdown();
    }
}
6. 線程流程圖在這裏插入圖片描述debug後可以ThreadPoolExecutor的execute方法中,可以知道他的執行策略(下下圖),線程池和阻塞隊列都繼承着同步隊列

在這裏插入圖片描述
在這裏插入圖片描述

7. 拒絕策略

線程池有四種拒絕策略(上圖的第四步):
AbortPolicy:拋出異常,默認
CallerRunsPolicy:不使用線程池執行
DiscardPolicy:直接丟棄任務
DiscardOldestPolicy:丟棄隊列中最舊的任務

對 於 線 程 池 選 擇 的 拒 絕 策 略 可 以 通 過 RejectedExecutionHandler handler = new
ThreadPoolExecutor.CallerRunsPolicy();來設置。
Jps>jstack
jstack 是排查線程死鎖

8. 簡單線程池監控
public class MonitorThreadPoolUtil implements Runnable {
    private ThreadPoolExecutor executor;
    private int seconds;
    private boolean run = true;
    public MonitorThreadPoolUtil(ThreadPoolExecutor executor,int delay){
        this.executor = executor;
        this.seconds = delay;
    }
    public void shutdown(){
        this.run = false;
    }
    @Override
    public void run() {
        while (run){
            if (this.executor.isTerminated()){
                System.out.println("任務執行完成");
                break;
            }
            System.out.println(
                    String.format("{monitor} 池大小:%d,核心數:%d,活躍數:%d,完成數:%d,任務數:%d ,線程結束沒:%b,任務執行完成沒:%b",
                            this.executor.getPoolSize(),
                            this.executor.getCorePoolSize(),
                            this.executor.getActiveCount(),
                            this.executor.getCompletedTaskCount(),
                            this.executor.getTaskCount(),
                            this.executor.isShutdown(),
                            this.executor.isTerminated()
                            ));
            try {
                Thread.sleep(seconds * 1000);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        new Thread(new MonitorThreadPoolUtil((ThreadPoolExecutor)executorService,1)).start();

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello MonitorThreadPool");
                try {
                    Thread.sleep( 10000);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
            }
        });
        executorService.shutdown();
    }
}

9. ScheduledExecutor詳解

後續

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章