徹底搞懂Java多線程(三)
Java線程池
線程的缺點:
1.線程的創建它會開辟本地方法棧、JVM棧、程序計數器私有的內存,同時消耗的時候需要銷毀以上三個區域,因此頻繁的創建和銷毀線程比較消耗系統的資源。
2.在任務量遠遠大於線程可以處理的任務量的時候,不能很好的拒絕任務。
所以就有瞭線程池:
使用池化的而技術來管理和使用線程。
線程池的優點
1.可以避免頻繁的創建和銷毀線程
2.可以更好的管理線程的個數和資源的個數。
3.線程池擁有更多的功能,比如線程池可以進行定時任務的執行。
4.線程池可以更友好的拒絕不能處理的任務。
線程池的6種創建方式
一共有7種創建方式
創建方式一:
創建固定個數的線程池:
package ThreadPoolDemo; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * user:ypc; * date:2021-06-13; * time: 10:24; */ public class ThreadPoolDemo1 { public static void main(String[] args) { //創建一個固定個數的線程池 ExecutorService executorService = Executors.newFixedThreadPool(10); //執行任務 for (int i = 0; i < 10; i++) { executorService.execute(new Runnable() { @Override public void run() { System.out.println("線程名" + Thread.currentThread().getName()); } }); } } }
那麼如果執行次數大於10次呢?
線程池不會創建新的線程,它會復用之前的線程。
那麼如果隻執行兩個任務呢?它創建瞭是10個線程還是兩個線程呢?
我們可以使用Jconsole
來看一看:
結果是隻有2個線程被創建。
創建方式二:
創建帶有緩存的線程池:
適用於短期有大量的任務的時候使用
public class ThreadPoolDemo2 { public static void main(String[] args) { //創建帶緩存的線程池 ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 100; i++) { executorService.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }); } } }
方式三:
創建執行定時任務的線程池
package ThreadPoolDemo; import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * user:ypc; * date:2021-06-13; * time: 11:32; */ public class ThreadPoolDemo3 { public static void main(String[] args) { ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2); System.out.println("執行定時任務前的時間:" + new Date()); scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("執行任務的時間:" + new Date()); } },1,2, TimeUnit.SECONDS); } }
執行任務的四個參數的意義:
參數1:延遲執行的任務
參數2:延遲一段時間後執行
參數3:定時任務執行的頻率
參數4:配合前兩個參數使用,是2、3參數的時間單位
還有兩種執行的方法:
隻會執行一次的方法:
第三種的執行方式:
那麼這種的執行方式和第一種的執行方式有什麼區別呢?
當在兩種執行的方式中分別加上sleep()之後:
方式一:
方式三:
結論很明顯瞭:
第一種方式是以上一個任務的開始時間+定時的時間作為當前任務的開始時間
第三種方式是以上一個任務的結束時間來作為當前任務的開始時間。
創建方式四:
package ThreadPoolDemo; import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * user:ypc; * date:2021-06-13; * time: 12:38; */ public class ThreadPoolDemo4 { public static void main(String[] args) { //創建單個執行任務的線程池 ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); System.out.println("執行任務之前" + new Date()); scheduledExecutorService.scheduleWithFixedDelay(new Runnable() { @Override public void run() { System.out.println("我是SingleThreadSchedule"+ new Date()); } },3,1, TimeUnit.SECONDS); } }
創建方式五:
創建單個線程的線程池
package ThreadPoolDemo; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * user:ypc; * date:2021-06-13; * time: 12:55; */ public class ThreadPoolDemo5 { public static void main(String[] args) { //創建單個線程的線程池 ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 20; i++) { executorService.execute(new Runnable() { @Override public void run() { System.out.println("線程名 " + Thread.currentThread().getName()); } }); } } }
創建單個線程池的作用是什麼?
1.可以避免頻繁創建和銷毀線程所帶來的性能的開銷
2.它有任務隊列,可以存儲多餘的任務
3.可以更好的管理任務
4.當有大量的任務不能處理的時候,可以友好的執行拒絕策略
創建方式六:
創建異步線程池根據當前CPU來創建對應個數的線程池
package ThreadPoolDemo; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * user:ypc; * date:2021-06-13; * time: 13:12; */ public class ThreadPoolDemo6 { public static void main(String[] args) { ExecutorService executorService = Executors.newWorkStealingPool(); for (int i = 0; i < 10; i++) { executorService.execute(new Runnable() { @Override public void run() { System.out.println("線程名" + Thread.currentThread().getName()); } }); } } }
運行結果為什麼什麼都沒有呢?
看下面的異步與同步的區別就知道瞭。
加上這個
就可以輸出結果瞭
線程池的第七種創建方式
前六種的創建方式有什麼問題呢?
1.線程的數量不可控(比如帶緩存的線程池)
2.工作任務量不可控(默認的任務隊列的大小時Integer.MAX_VALUE),任務比較大肯會導致內存的溢出。
所以就可以使用下面的創建線程池的方式瞭:
package ThreadPoolDemo; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * user:ypc; * date:2021-06-13; * time: 15:05; */ public class ThreadPoolDemo7 { private static int threadId = 0; public static void main(String[] args) { ThreadFactory threadFactory = new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("我是threadPool-" + ++threadId); return thread; } }; ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 3, 100, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(12), threadFactory, new ThreadPoolExecutor.AbortPolicy()); for (int i = 0; i < 15; i++) { threadPoolExecutor.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }); } } }
參數說明:
- 參數一:核心線程數|線程池正常情況下的線程 數量
- 參數二:最大線程數|當有大量的任務的時候可以創建的最多的線程數
- 參數三:最大線程的存活時間
- 參數四:配合參數三一起使用的表示參數三的時間單位
- 參數五:任務隊列
- 參數六:線程工廠
- 參數七:決絕策略
註意事項:最大的線程數要大於等於核心的線程數
五種拒絕策略
為什麼拒絕策略可以舍棄最新的任務或者最舊的任務呢?
因為LinkedBlockingDeque時FIFO的。
第五種:自定義的拒絕策略
ThreadPoolExecutor的執行方式
package ThreadPoolDemo; import java.util.concurrent.*; /** * user:ypc; * date:2021-06-13; * time: 16:58; */ public class ThreadPoolDemo9 { public static void main(String[] args) throws ExecutionException, InterruptedException { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 4, 100, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10), new ThreadPoolExecutor.DiscardOldestPolicy()); //線程池的執行方式一 threadPoolExecutor.execute(new Runnable() { @Override public void run() { System.out.println("使用瞭execute()執行瞭線程池"); } }); //線程池的執行方式二 Future<String> futureTask = threadPoolExecutor.submit(new Callable<String>() { @Override public String call() throws Exception { return "使用submit(new Callable<>())執行瞭線程池"; } }); System.out.println(futureTask.get()); } }
無返回值的執行方式
有返回值的執行方式
ThreadPoolExecutor的執行流程
當任務量小於核心線程數的時候,ThreadPoolExecutor會創建線程來執行任務
當任務量大於核心的線程數的時候,並且沒有空閑的線程時候,且當線程池的線程數小於最大線程數的時候,此時會將任務存
放到任務隊列中
如果任務隊列也被存滿瞭,且最大線程數大於線程池的線程數的時候,會創建新的線程來執行任務。
如果線程池的線程數等於最大的線程數,並且任務隊列也已經滿瞭,就會執行拒絕策略。👇
線程池的終止
shutdown()
線程池的任務會執行完
shutdownNow()
立即終止線程池,線程池的任務不會執行完
線程池的狀態
異步、同步
1.Java 線程 同步與異步
多線程並發時,多個線程同時請求同一個資源,必然導致此資源的數據不安全,A線程修改瞭B線程的處理的數據,而B線程又修改瞭A線程處理的數理。顯然這是由於全局資源造成的,有時為瞭解決此問題,優先考慮使用局部變量,退而求其次使用同步代碼塊,出於這樣的安全考慮就必須犧牲系統處理性能,加在多線程並發時資源掙奪最激烈的地方,這就實現瞭線程的同步機制
同步
A線程要請求某個資源,但是此資源正在被B線程使用中,因為同步機制存在,A線程請求不到,怎麼辦,A線程隻能等待下去
異步
A線程要請求某個資源,但是此資源正在被B線程使用中,因為沒有同步機制存在,A線程仍然請求的到,A線程無需等待同步的方式:
1.發送請求
2.等待執行完成
3.有結果的返回
異步的方式
1.發請求
2.執行完成
3.另一個線程異步處理
4.處理完成之後返回回調結果
顯然,同步最最安全,最保險的。而異步不安全,容易導致死鎖,這樣一個線程死掉就會導致整個進程崩潰,使用異步的機制,性能會有所提升
線程工廠
設想這樣一種場景,我們需要一個線程池,並且對於線程池中的線程對象,賦予統一的線程優先級、統一的名稱、甚至進行統一的業務處理或和業務方面的初始化工作,這時工廠方法就是最好用的方法瞭
package ThreadPoolDemo; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; /** * user:ypc; * date:2021-06-13; * time: 11:12; */ public class ThreadFactoryDemo { public static void main(String[] args) { MyThreadFactory myThreadFactory = new MyThreadFactory(); ExecutorService executorService = Executors.newFixedThreadPool(10,myThreadFactory); for (int i = 0; i < 10; i++) { executorService.execute(new Runnable() { @Override public void run() { System.out.println("使用線程工廠設置的線程名:"+ Thread.currentThread().getName() + " 使用線程工廠設置的線程的優先級" + Thread.currentThread().getPriority()); } }); } } private static int count = 0; static class MyThreadFactory implements ThreadFactory{ @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setPriority(8); thread.setName("thread--" + count++); return thread; } } }
總結
本篇文章就到這裡瞭,希望可以對你有所幫助,也希望您能夠多多關註WalkonNet的更多內容!
推薦閱讀:
- Java線程池的簡單使用方法實例教程
- Java並發編程之Executors類詳解
- java線程池詳解及代碼介紹
- Java並發編程之Executor接口的使用
- java多線程CountDownLatch與線程池ThreadPoolExecutor/ExecutorService案例