Java17(線程池、Runnable和Callable、Lock、信號量、任務調度、Timer)


一、線程池概念

    在Java中,如果每個請求到達就會創建一個新線程,開銷是相當大的。

  線程池就是來解決生命周期開銷問題和資源不足問題。通過多任務重復使用線程,線程創建的開銷被分攤到多個任務上,而且由於在請求到達前時線程已經存在,所以消除了線程創建所帶來的延遲。這樣,就可以立即為請求服務,是應用程序響應更快。

  通過適當的調整線程中的線程數目,可以防止資源不足的情況。

  當服務器接受到大量短小的請求時,使用線程池技術是非常合適的,他可以大大減少線程的創建和銷毀次數,提高服務器的工作效率。  

  一個比較簡單線程池應該包括:

    線程池管理器:

      創建、銷毀並管理線程池,將工作線程放入池中

    工作線程:

      一個可以循環執行任務的線程,在沒有任務時進行等待

    任務隊列:

      提供一種緩沖機制,將沒有處理的任務放在任務隊列中

    任務接口:

      每個任務必須實現的接口,主要用來規定任務的入口、任務執行完后的收尾工作、任務的執行狀態等,工作線程通過該接口調度任務的執行

   簡易線程池代碼:

 1 package com.chinasofti.executor;
 2 
 3 public class ExecutorThread extends Thread {
 4     // 創建一個屬性表示是否運行項目
 5     boolean isRunning = false;
 6     // 創建一個Runnable接口表示執行的方法
 7     private Runnable run;
 8 
 9     public synchronized void setRunning(boolean running) {
10         isRunning = running;
11         // 如果運行項目運行 那么就喚醒線程
12         if(isRunning){
13            this.notify();
14         }
15     }
16 
17     public boolean isRunning() {
18         return isRunning;
19     }
20 
21     public Runnable getRun() {
22         return run;
23     }
24 
25     public void setRun(Runnable run) {
26         this.run = run;
27     }
28 
29     @Override
30     public synchronized void run() {
31         while (true){
32             // 如果項目運行 就執行規定的方法
33             if(isRunning){
34                 this.run.run();
35                 isRunning = false;
36             // 否則就繼續等待
37             }else {
38                 try {
39                     this.wait();
40                 } catch (InterruptedException e) {
41                     e.printStackTrace();
42                 }
43             }
44         }
45     }
46 }
View Code
 1 package com.chinasofti.executor;
 2 
 3 import java.util.ArrayList;
 4 import java.util.Collections;
 5 import java.util.List;
 6 
 7 public class ThreadPoolExecutor {
 8     // 創建線程池容器
 9     private List<ExecutorThread> list;
10     // 創建構造方法 num表示線程池的數量
11     public ThreadPoolExecutor(int num) {
12         // new一個線程池的集合,轉換成線程安全集合
13         this.list = Collections.synchronizedList(new ArrayList<ExecutorThread>());
14         // 循環創建num個線程並執行
15         for (int i = 0; i < num; i++) {
16             list.add(new ExecutorThread());
17             list.get(i).start();
18         }
19     }
20 
21     // 創建一個線程池工作的方法
22     public void execute(Runnable run){
23         // 遍歷線程池中的線程 判斷是否正在運行
24         int i;
25         for ( i = 0;  i < list.size();  i++) {
26             ExecutorThread et = list.get(i);
27             // 如果當前線程沒有運行 則添加方法
28             if(!et.isRunning()){
29                 et.setRun(run);
30                 et.setRunning(true);
31                 return;
32             }
33         }
34         // 如果線程池的線程均在運行 則提示
35         if(i==list.size()){
36             System.out.println("線程池中線程用盡");
37         }
38     }
39 
40 
41 }
View Code

 

 1 package com.chinasofti.executor;
 2 
 3 public class Test {
 4     public static void main(String[] args) {
 5         // 先new一個池子
 6         ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5);
 7         // 使用池子中的線程 執行方法
 8         threadPoolExecutor.execute(()->{
 9             for (int i = 0; i < 8; i++) {
10                 try {
11                     Thread.sleep(500);
12                 } catch (InterruptedException e) {
13                     e.printStackTrace();
14                 }
15                 System.out.println(Thread.currentThread().getName() + "---" + i);
16             }
17         });
18 
19         threadPoolExecutor.execute(()->{
20             for (int i = 0; i < 8; i++) {
21                 try {
22                     Thread.sleep(500);
23                 } catch (InterruptedException e) {
24                     e.printStackTrace();
25                 }
26                 System.out.println(Thread.currentThread().getName() + "---" + i);
27             }
28         });
29 
30         threadPoolExecutor.execute(()->{
31             for (int i = 0; i < 8; i++) {
32                 try {
33                     Thread.sleep(500);
34                 } catch (InterruptedException e) {
35                     e.printStackTrace();
36                 }
37                 System.out.println(Thread.currentThread().getName() + "---" + i);
38             }
39         });
40 
41         threadPoolExecutor.execute(()->{
42             for (int i = 0; i < 8; i++) {
43                 try {
44                     Thread.sleep(500);
45                 } catch (InterruptedException e) {
46                     e.printStackTrace();
47                 }
48                 System.out.println(Thread.currentThread().getName() + "---" + i);
49             }
50         });
51 
52         threadPoolExecutor.execute(()->{
53             for (int i = 0; i < 8; i++) {
54                 try {
55                     Thread.sleep(500);
56                 } catch (InterruptedException e) {
57                     e.printStackTrace();
58                 }
59                 System.out.println(Thread.currentThread().getName() + "---" + i);
60             }
61         });
62 
63         threadPoolExecutor.execute(()->{
64             for (int i = 0; i < 8; i++) {
65                 try {
66                     Thread.sleep(500);
67                 } catch (InterruptedException e) {
68                     e.printStackTrace();
69                 }
70                 System.out.println(Thread.currentThread().getName() + "---" + i);
71             }
72         });
73 
74 
75     }
76 }
View Code

 

  Executor框架:

    引入Executor的框架的最大優點就是把任務和執行解耦,要執行任務只需要把task描述清楚,然后提交即可。至於這個task是如何執行,被誰執行,什么時候執行,提交的人就不必要擔心了。具體點說,提交一個Callable對象,給ExecutorService(如最常用的線程池ThreadPoolExecutor),將得到一個future對象,調用future對象的get方法等待執行結果就好了。

    

 

 

       

 

  Future對象的api

    

 

 

   Executor是一個可以提交可執行任務的工具,這個接口解耦了任務提交和執行的細節。Executor主要來替代顯示的創建和運行線程。

  ExecutorService提供了異步的管理一個或多個線程終止、執行過程的方法。

  Executors類提供了一系列工廠方法用於創建任務執行器,返回的任務執行器都都實現了ExecutorService接口。

           

  有幾種不同的方式用來將任務委托給一個ExecutorService:

    execute(Runnable):接受一個runnable對象,異步的去執行它,使用這種方法沒有辦法獲取執行后的runnable結果,如果你希望獲取運行之后接受返回值,那么就必須使用接受Callable參數的submit()方法。

    submit(Runnable):接受一個runnable對象,但是會返回一個future對象,這個對象可以用於判斷runnable任務是否結束執行。如果使用future的get方法,那么因為runnable沒有返回值,所以只能get到null值。

    submit(Callable):接受一個Callable對象,,但是會返回一個future對象,Callable和Runnable相似,只不過Callable中是call方法,Runnable中是run方法,而且Callable中可以有返回值,而Runnable沒有返回值,返回的future對象使用get可以獲取Callable返回的結果。

    invokeAny(Callable集合):接受一個裝有Callable對象的集合,不會返回future對象,而是會返回這個集合中某一個Callable執行后的結果(返回值),如果一個任務運行完畢或者拋出異常,方法會取消其他Callable的執行。

    invokeAll(Callable集合):接受一個裝有Callable對象的集合,返回future對象的集合,通過這個future集合可以管理所有Callable的執行結果。不過要注意,任務可能因為異常而導致運行結束,所以他可能不是真的執行結束,我們沒辦法通過future對象來了解這個差異。

  當我們使用完ExecutorService之后,應該手動關閉它,不然虛擬機仍會保持運行。

  如果你的程序通過main() 方法啟動,並且主線程退出了你的程序,如果你還有一個活動的 ExecutorService存在於程序中,那么程序將會繼續保持運行狀態。存在於ExecutorService中的活動線程會阻止Java虛擬機關閉

  

 1 package com.chinasofti.executor.reS;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 import java.util.concurrent.Callable;
 6 import java.util.concurrent.ExecutorService;
 7 import java.util.concurrent.Executors;
 8 
 9 public class TestExitMain {
10     public static void main(String[] args) throws InterruptedException {
11         ExecutorService executorService = Executors.newCachedThreadPool();
12 
13         List<Callable<String>> list = new ArrayList<Callable<String>>();
14         list.add(()->{
15             System.out.println("zbc5線程還在運行");
16             return "zbc5";
17         });
18         list.add(()->{
19             System.out.println("zbc2線程還在運行");
20             return "zbc2";
21         });
22         list.add(()->{
23             System.out.println("zbc3線程還在運行");
24             return "zbc3";
25         });
26         list.add(()->{
27             System.out.println("zbc4線程還在運行");
28             return "zbc4";
29         });
30         executorService.invokeAll(list);
31         System.out.println("主線程已退出");
32         Thread.currentThread().stop();
33         System.out.println("測試主線程退出后是否輸出");
34     }
35 }

 

 

 

  執行結果:

    

 

 

       為了關閉在 ExecutorService 中的線程,需要調用 shutdown() 方法。ExecutorService 並不會馬上關閉,而是不再接收新的任務,一旦所有的線程結束執行當前任務,ExecutorServie才會真的關閉。所有在調用shutdown()方法之前提交到ExecutorService的任務都會執行

   如果希望立即關閉ExecutorService,可以調用shutdownNow()方法。它會嘗試馬上關閉所有正在執行的任務,並且跳過所有已經提交但是還沒有運行的任務。但是對於正在執行的任務,是否能夠成功關閉它是無法保證的,有可能他們真的被關閉掉了,也有可能它會壹直執行到任務結束

 

 二、Runnable和Callable區別

  Runnable:提供的任務方法為run(),沒有返回值

  Callable:提供的任務方法是call(),有返回值

  兩個都是符合函數式接口

三、Lock

  Lock其實和synchronized功能相似,只是Lock更具有語義化,可以更加清楚的表達清楚何時加鎖何時釋放鎖。

  JDK1.5中,提供了一個新的所對象Lock(是個接口),默認實現類是:ReentrantLock:

    可重入的獨占鎖,有着一些synchronized關鍵字擴展的功能。使用lock加鎖,使用unlock解鎖。該類有一個重要特性體現在構造器上,構造器接受一個可選參數,是否是公平鎖。

    公平鎖:

      先來的一定排隊,一定先獲得鎖

    非公平鎖:

      不保證上述條件,非公平的吞吐量更高。

 

 

    

 1 package com.chinasofti.executor;
 2 
 3 import java.util.concurrent.locks.Lock;
 4 import java.util.concurrent.locks.ReentrantLock;
 5 
 6 public class LockTest {
 7     public static void main(String[] args) {
 8         Print print = new Print();
 9 
10         Thread t1 = new Thread(()->{
11             try {
12                 print.ma();
13             } catch (InterruptedException e) {
14                 e.printStackTrace();
15             }
16         });
17 
18         Thread t2 = new Thread(()->{
19             try {
20                 print.mb();
21             } catch (InterruptedException e) {
22                 e.printStackTrace();
23             }
24         });
25 
26         Thread t3 = new Thread(()->{
27             try {
28                 print.mc();
29             } catch (InterruptedException e) {
30                 e.printStackTrace();
31             }
32         });
33 
34         t1.start();
35         t2.start();
36         t3.start();
37     }
38 
39 }
40 class Print{
41     Lock lock = new ReentrantLock();
42 
43     public void ma() throws InterruptedException {
44         lock.lock();
45         for (int i = 0; i < 10; i++) {
46             Thread.sleep(500);
47             System.out.println(Thread.currentThread().getName() + "---" + i);
48         }
49         lock.unlock();
50     }
51 
52     public void mb() throws InterruptedException {
53         lock.lock();
54         for (int i = 0; i < 10; i++) {
55             Thread.sleep(500);
56             System.out.println(Thread.currentThread().getName() + "---" + i);
57         }
58         lock.unlock();
59     }
60 
61     public void mc() throws InterruptedException {
62         lock.lock();
63         for (int i = 0; i < 10; i++) {
64             Thread.sleep(500);
65             System.out.println(Thread.currentThread().getName() + "---" + i);
66         }
67         lock.unlock();
68     }
69 }

 

 

 

   ThreadLocal:

      ThreadLocal用來解決多線程間的並發問題(線程間沒有各自獨立的變量)

    ThreadLoacl不是一個Thread,而是Thread的一個局部變量,當使用ThreadLoacl維護變量時,ThreadLocal為每個使用變量的線程都提供了一個副本變量,所以每個線程都能獨立的使用自己的副本變量,不會影響到其他線程的副本變量。

    方法:  

      void set(T value):將此線程局部變量的當前線程副本中的值設置為指定值

      void remove():移除此線程局部變量當前線程的值

      protected T initalValue():返回此線程局部變量的當前線程的“初始值”

      T get():返回此線程中局部變量的當前線程副本中的值

    

 

 

 四、信號量

          

 

    信號量,有時候被稱為信號燈,是在多線程環境下使用的一種設施,他負責協調各個線程,以保證他們能夠正常、合理的使用公共資源。

    一個計數信號量,從概念上來說,信號量維護了一個許可集。如有必要,在許可可用前會阻塞每一個請求資源的線程,然后再獲取該許可。信號量提供一個方法添加一個許可,從而可釋放一個正在阻塞的獲取者。

    信號量對可用許可進行計數,並采取相應的行動,拿到信號量許可的線程可以進入代碼,否則就等待。通過申請和釋放方法獲取和釋放訪問許可。

     

 

 

  

    總結:

    線程池主要是用來解決線程生命周期開銷問題和資源不足問題。通過多個任務重復使用線程,線程創建的開銷就被分攤到多個任務上,而且由於請求到達時線程已經存在,所以消除了線程創建所帶來的延遲,這樣就可以更快的響應。

    信號量被稱為信號燈,是在多線程下使用的一種設施,他負責協調各個線程,以保證他們能夠正確、合理的使用公共資源。一個計數信號量,從概念上來說,信號量維護了一個許可集。如有必要,信號量會阻塞請求許可的資源,然后再獲得該許可。信號量提供了一個方法添加一個許可,從而可能釋放一個正在阻塞的獲取者。

    Executor是一個可以提交可執行任務的工具,這個接口解耦了提交和執行任務的細節,Executor主要來替代顯示的創建和運行線程。

    Runnable和Callable都是可以抽象任務供Executor執行,不同的是Runnable沒有返回值,Callable有返回值。

    ThreadLoacl用來解決多線程的並發問題,ThreadLoacl並不是一個Thread,而是Thread的一個局部變量,ThreadLoacl在維護局部變量時,ThreadLocal為每個線程提供了一個獨立副本,每個線程之間互不干擾。

    

五、Timer

  Timer是Java最早提供的一個任務調度器,它可以支持定時任務和重復任務的調度。

  Timer通過一個獨立的線程通過wait/notify機制對所有的任務進行調度,但是用戶無需關心內部的線程實現細節,僅需通過Timer類的相關調度方法來實現任務的調度。

  

  Timer調度方法的第一個參數都是TimerTask的對象,TimerTask是所執行任務的代碼。

  

 

 

 

  注意,由於Java要考慮跨平台性,又因為每個平台的線程調度機制不同,從而Timer不能保證任務能夠按時完成。由於TimerTask是由Runnable接口實現的,在TimerTask被放進線程隊列中睡眠一段時間(wait)后,當到了指定喚醒該TimerTask時,由於JVM的調度策略,以及不知道還有多少線程在等待CPU處理,因此不能保證線程執行的時間一定准確。通常有兩種情況下導致任務延遲執行:

    1.有大量的線程等待執行

    2.GC機制的影響導致延遲

 1 package com.chinasofti.executor;
 2 
 3 import java.util.Date;
 4 import java.util.Timer;
 5 import java.util.TimerTask;
 6 
 7 public class TimerTest {
 8     public static void main(String[] args) {
 9         Timer timer = new Timer();
10         // 立即執行
11         timer.schedule(new Printt(),new Date());
12         // 延時一秒執行
13         timer.schedule(new Printt2(),1000);
14         // 延時一秒后每隔0.5s重復執行
15         timer.schedule(new Printt(),1000,1000);
16         // 每隔0.5s重復
17         timer.schedule(new Printt2(),new Date(),1000);
18     }
19 
20 
21 }
22 
23 class Printt extends TimerTask {
24     @Override
25     public void run() {
26         System.out.println("print");
27     }
28 }
29 class Printt2 extends TimerTask {
30     @Override
31     public void run() {
32         System.out.println("print222");
33     }
34 }

  

                         

        

 

 

      

 1 package com.chinasofti.executor;
 2 
 3 import java.util.concurrent.ScheduledThreadPoolExecutor;
 4 import java.util.concurrent.TimeUnit;
 5 
 6 public class ScheduledThreadPoolExecutorTest {
 7     public static void main(String[] args) {
 8         ScheduledThreadPoolExecutor stpe = new ScheduledThreadPoolExecutor(5);
 9 
10         // 第一個參數是TimerTask對象,第二個參數是延時時間,第三個參數是延時時間的單位
11         stpe.schedule(()->{
12             System.out.println("哈哈哈哈哈哈哈哈奧或哈奧奧奧奧奧奧奧奧奧奧奧奧奧奧奧奧奧奧奧奧奧奧奧奧奧奧奧奧奧奧");
13         },10, TimeUnit.SECONDS);
14     }
15 }

 

      

  • Quartz是一個完全由java編寫的開源任務調度框架。盡管Quartz框架整合了許多額外功能, 但就其簡易形式看,它非常易用。簡單地創建一個實現org.quartz.Job接口的java類。Job接口包含唯一的方法:

public void execute(JobExecutionContext context) throws JobExecutionException;

  • 在Job接口實現類里面,添加一些業務代碼到execute()方法。一旦配置好Job實現類並設定好調度時間表,Quartz將密切注意剩余時間。當調度程序確定該是通知執行任務的時候,Quartz框架將調用Job實現類上的execute()方法並允許做它該做的事情。無需報告任何東西給調度器或調用任何特定的東西。僅僅執行任務和結束任務即可。如果配置你的作業在隨后再次被調用,Quartz框架將在恰當的時間再次調用它
  • 而這個過程和Timer/TimerTask的搭配十分類似
  • Quartz框架的核心是調度器。調度器負責管理Quartz應用運行時環境。調度器不是靠自己做所有的工作,而是依賴框架內一些非常重要的部件。Quartz不僅僅是線程和線程管理。為確保可伸縮性,Quartz采用了基於多線程的架構。啟動時,框架初始化一套worker線程,這套線程被調度器用來執行預定的作業。這就是Quartz怎樣能並發運行多個作業的原理。Quartz依賴一套松耦合的線程池管理部件來管理線程環境

 

 

 

 

 

 

 

 

  總結:

  • Timer和TimerTask是什么關系?
  • Timer和ScheduledThreadPoolExecutor有什么區別?
  • Quartz的功能是什么?
  • Timer是Java最早提供的一個任務調度器,它可以支持定時任務和重復任務的調度,如果希望借助於Timer進行任務調度,必須要滿足Timer對任務本身的抽象規定,從剛才的Timer調度方法列表中可以看出,所有的方法第一個共同的參數均為TimerTask的對象,用於聲明需要調度的任務中需要執行的指令代碼
  • 一個Timer調度器的所有任務都運行在一個線程中,存在單個任務出現異常導致所有任務不能執行的隱患,而JDK5之后的ScheduledThreadPoolExecutor提供了並發任務調用,不存在這個隱患
  • Quartz是一個完全由java編寫的開源任務調度框架 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM