Java十分鐘入門多線程下篇
1、線程池:
什麼是線程池?
咱們也不看長篇大論,通俗的來講,線程池就是裝線程的容器,當需要用的時候去池裡面取出來,不用的時候放回去或者銷毀。這樣一個線程就可以反復的利用,通過線程的這種反復利用機制,可以有效地避免直接創建線程所帶來的壞處。
線程池有什麼好處?
- 降低瞭資源的消耗(CPU)
- 提高任務執行的響應速度
- 提高線程的可管理性
線程池創建流程圖:
其實通過這個圖就可以看到線程池的處理過程:
- 有新任務進來,判斷核心線程池是否滿瞭,是:進入排,否:創建任務
- 在等待隊列判斷是否排滿瞭,是:進入線程池 ,否:任務加入隊列
- 判斷線程池是否滿瞭,是:拒絕執行任務,否:創建線程執行
2、創建線程池:
先看一下官網給出的創建方法(部分):
完整的可以參考官方文檔:
https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/Executors.html
這裡介紹四種常用的創建類型:
- newCacheThreadPool(創建一個可緩存的線程池,有任務時才會創建新的線程)
- newSingleThreadExecutor(創建一個單線程池,線程池中隻有一個線程)
- newFixedThreadPool(int a) (創建固定線程數量的線程池,輸入參數:int類型)
- newScheduledTreadPool (創建一個固定長度的線程池,並且以延時或定時的方式來執行線程)
1、newCacheThreadPool:
創建可緩存的線程對象,意思是這個任務需要幾個線程來處理,就會創建幾個線程:
線程需要執行的類:
public class MyRunnable implements Runnable{ int num; public MyRunnable(int num) { this.num = num; } @Override public void run() { System.out.println(Thread.currentThread().getName()+"執行瞭:"+num); } }
測試類:
public class Test { public static void main(String[] args) { //創建單個線程池對象,裡面線程隻有一個 ExecutorService cachedService = Executors.newCachedThreadPool(); //執行5個任務 for (int i = 1; i<= 5; i++) { cachedService.execute(new MyRunnable(i)); } } }
來看看結果:
OK,上述代碼線程池用瞭5個線程來處理,那麼如果我們在每次運行前加一次線程休眠會怎麼樣? 在每次執行後需要休眠0.5秒(500毫秒):
public class Test { public static void main(String[] args) { //創建一個緩存的線程池對象 ExecutorService cachedService = Executors.newCachedThreadPool(); for (int i = 1; i<= 5; i++) { cachedService.execute(new MyRunnable(i)); //線程休眠: try{ Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
看看結果:
小結:
- 如果沒有加線程休眠,線程池默認會創建多個線程池對象來幫你完成任務,執行更快完成並且銷毀內存,釋放資源。
- 如果添加瞭線程休眠,線程池會認為同少量的線程對象就可以完成這個任務,就不會幫你創建多個線程對象(因為時間足夠,就沒必要再創建)
2、newSingleThreadExecutor:
這個方法是創建隻有一個線程的線程池,不管怎麼樣,多隻有一個線程來幫你執行任務:
public class Test { public static void main(String[] args) { //創建單個線程池對象,裡面線程隻有一個 ExecutorService singleService = Executors.newSingleThreadExecutor(); //執行一百萬次任務 for (int i = 1; i<= 1000000; i++) { singleService.execute(new MyRunnable(i)); } } }
看看結果:
對吧,執行瞭1000000次也是一個線程在執行,因為這個線程池裡面隻有一個線程呀。
3、newFixedThreadPool(int a):
這個方法就是創建固定線程數的線程池,比如我要一個這個池裡面有10個線程,在後面輸入參數即可:
public class Test { public static void main(String[] args) { //線程池創建固定數量的線程對象來執行任務,這裡創建10個線程對象,由線程池管理生命周期 ExecutorService singleService = Executors.newFixedThreadPool(10); //執行10個任務 for (int i = 1; i<= 100; i++) { singleService.execute(new MyRunnable(i)); } } }
看看結果:
4、newScheduledTreadPool :
創建一個固定長度的線程池,並且以延時或定時的方式來執行線程,也就是說使用這個方法創建的線程池可以自定義每次執行的時間:
Demo:
public class MyRunnable implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"延遲5秒執行,並且每2秒執行一次該任務:"); } }
測試類:
public class Test { public static void main(String[] args) { //線程池創建一個定時任務的線程對象 ScheduledExecutorService service = Executors.newScheduledThreadPool(4); /** *定時器執行的任務,並且按周期執行 * 參數1:線程任務 * 參數2:5秒之後開始執行 * 參數3:執行後每2秒為一個周期去循環執行 * 參數4:時間單位。枚舉類型,我這指定秒(可以參考API) */ service.scheduleAtFixedRate(new MyRunnable(),5,1, TimeUnit.SECONDS); } }
看看結果:
3、線程池創建自定義線程:
當以上四種線程池滿不足業務需求的時候,咱們也可以自定義線程池:
先來一個線程執行類:
public class MyRunnable implements Runnable{ int num; public MyRunnable(int i) { this.num = num; } @Override public void run() { System.out.println(Thread.currentThread().getName()+"執行瞭"+num); } }
測試類:
public class Test { public static void main(String[] args) { /** * 參數1:線程池有5個固定的線程對象 * 參數2:當線程池的線程忙不過來的時候,最大線程數為7,也就是說線程對象就可以增加到7個(其中2個是臨時線程) * 參數3:當臨時線程對象超出300毫秒的時候還沒有接到新任務,線程池就自動 銷毀臨時的線程對象 * 參數4:時間單位。這指定秒,具體參考TimeUnit的枚舉類型 * 參數5:等待執行的任務序列4個(其中有4個任務序列等待線程池對象來執行任務) */ ThreadPoolExecutor executor = new ThreadPoolExecutor(5,7,300, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(4)); for (int i = 0; i < 6; i++) { executor.execute(new MyRunnable(i)); showInfo(executor); } } //獲取線程的一些信息 private static void showInfo(ThreadPoolExecutor executor) { System.out.println("線程池的總數量:"+executor.getPoolSize()); System.out.println("隊列中等待執行的任務數:"+executor.getQueue().size()); System.out.println("已經執行完畢的任務數:"+executor.getCompletedTaskCount()); System.out.println("---------"); } }
4、Runnable和Callable的區別:
Runnable是不返還值的,而Callable可以返回值,具體可以看這篇博客(其實主要我也沒深入學習這個哈哈): Runnable和Callable的區別
5、線程池總結:
通過這個文章,咱們對線程池有瞭基礎的瞭解,如何去創建和使用,但這僅僅是最簡單的,線程是一個復雜的東西,大傢也可以參考其他技術博客來深入學習和研究,在創建線程池的時候,根據場景,合理設置線程池的各個參數,包括線程池數量、隊列、線程工廠和拒絕策略,讓資源更好的利用起來,這也是優化性能的關鍵。
到此這篇關於Java十分鐘入門多線程下篇的文章就介紹到這瞭,更多相關Java 多線程內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!