天天看點

Java多線程(四) 線程池

  一個優秀的軟體不會随意的建立、銷毀線程,因為建立和銷毀線程需要耗費大量的CPU時間以及需要和記憶體做出大量的互動。是以JDK5提出了使用線程池,讓程式員把更多的精力放在業務邏輯上面,弱化對線程的開閉管理。

  JDK提供了四種不同的線程池給程式員使用  

  首先使用線程池,需要用到ExecutorService接口,該接口有個抽象類AbstractExecutorService對其進行了實作,ThreadPoolExecutor進一步對抽象類進行了實作。最後JDK封裝了一個Executor類對ThreadPoolExecutor進行執行個體化,是以通過Executor能夠建立出具有如下四種特性的線程池

  1. 無下界線程池

        ExecutorService threadPool= Executors.newCachedThreadPool( ); 當線程數不足時,線程池會動态增加線程進行後續的任務排程

  2. 固定線程數的線程池

         ExecutorService threadPool= Executors.newFixedThreadPool(3); 建立具有固定線程數:3個的線程池

  3. 單線程線程池

   ExecutorService threadPool= Executors.newSingleThreadScheduledExecutor( );建立單例線程池,確定線程池内恒定有1條線程在工作

  4. 排程線程池

     ScheduledExecutorService threadPool= Executors.newSingleThreadScheduledExecutor( ); 建立一個定長線程池,定時或周期性地執行任務

Java多線程(四) 線程池
Java多線程(四) 線程池

1 package com.scl.thread.threadPool;
 2 
 3 import java.util.concurrent.Executors;
 4 import java.util.concurrent.ScheduledExecutorService;
 5 import java.util.concurrent.TimeUnit;
 6 
 7 public class ScheduledThreadPool
 8 {
 9     public static void main(String[] args)
10     {
11         // 建立定時排程線程池
12         ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
13         // 定時執行Runnable
14         threadPool.scheduleAtFixedRate(new Runnable()
15         {
16 
17             @Override
18             public void run()
19             {
20                 System.out.println("do some big Scheduled");
21             }
22         }, 10, 3, TimeUnit.SECONDS);
23     }
24 }      

定時排程線程池

        使用線程池也比較簡單,隻要往線程池裡面送出"任務"即可,一般使用對象.submit的方法進行,如下:

   ① threadPool.submit(Callable<T>task);    

               Callable跟Runnable接口一樣在使用接口時會自動調用裡面的call方法。與Runnable接口不一樣的地方在于run方法不能傳回内容,call方法含有傳回參數,在外部可以通過Future 接口來接受線程傳回的結果。如:Future<String> future = threadPool.submit(new MyTask( ));

         ② threadPool.submit(Runnable task);

            由這個簽名可以看出,同樣地可以往線程池裡面扔入Runnable接口執行個體。

  這如上面所說,線程池的使用就是減少程式員對線程的啟動和關閉。把線程的建立及關閉交給線程池來完成。是以使用線程池來完成線程的經典問題:生産者-消費者模型。

  模型簡介:該模型是一個典型的排隊内容。模型分為三個主要角色:生産者、消費者、倉庫。生産者和消費者共享倉庫資訊,①當倉庫裡面的産品滿了,停止生産等待消費者去消費到足夠的量再進行生産  ②當倉庫裡面的産品為空,等待生産者生産後再進行銷售  ③倉庫負責承載産品内容

  根據模型内容,做成了一個車位管理的例子。分為三個角色:持車人(CarOwner)、保安(Secure)、車庫(CarPark)。不同的持車人把車駛入到車庫,保安負責記錄車輛離開車庫的數量。車庫容量固定,如果車庫為空保安可以通知持車人把車駛入車庫。如果車庫滿了,保安告知持車人等待另外的持車人把車駕駛出來。車庫使用一個清單對入庫的車資訊進行記錄。

  是以有如下類圖:

                           

Java多線程(四) 線程池
Java多線程(四) 線程池
Java多線程(四) 線程池
1 package com.scl.thread.threadPool.CarParkManager;
 2 
 3 public class CarOwner implements Runnable
 4 {
 5     private int driverInNum;
 6     private CarPark carPark;
 7 
 8     public CarOwner(CarPark carPark)
 9     {
10         this.carPark = carPark;
11     }
12 
13     @Override
14     public void run()
15     {
16         carPark.driverIn(driverInNum);
17     }
18 
19     public int getDriverInNum()
20     {
21         return driverInNum;
22     }
23 
24     public void setDriverInNum(int driverInNum)
25     {
26         this.driverInNum = driverInNum;
27     }
28 }      

持車人 CarOwner

Java多線程(四) 線程池
Java多線程(四) 線程池
1 package com.scl.thread.threadPool.CarParkManager;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 import java.util.UUID;
 6 
 7 public class CarPark
 8 {
 9     protected int MaxCarNum;
10     private List<Car> carList = new ArrayList<Car>();
11 
12     public CarPark(int maxNum)
13     {
14         this.MaxCarNum = maxNum;
15     }
16 
17     public void driverIn(int num)
18     {
19         synchronized (carList)
20         {
21             while (num + carList.size() > MaxCarNum)
22             {
23                 System.out.println(Thread.currentThread() + ":無法進庫,目前車庫數目:" + carList.size());
24                 try
25                 {
26                     carList.wait();
27                 }
28                 catch (InterruptedException e)
29                 {
30                     e.printStackTrace();
31                 }
32             }
33             for (int i = 0; i < num; i++)
34             {
35                 try
36                 {
37                     Thread.sleep(20);
38                 }
39                 catch (InterruptedException e)
40                 {
41                     e.printStackTrace();
42                 }
43                 String uuid = UUID.randomUUID().toString();
44                 Car c = new Car(uuid, uuid + i);
45                 carList.add(c);
46             }
47             System.out.println(Thread.currentThread() + ":已經駛入" + num + "輛,目前車庫數目:" + carList.size());
48             carList.notify();
49         }
50     }
51 
52     public void driverOut(int num)
53     {
54         synchronized (carList)
55         {
56 
57             while (carList.size() < num)
58             {
59                 System.out.println(Thread.currentThread() + ":無法駛出" + num + "輛,目前車庫數目:" + carList.size());
60                 try
61                 {
62                     carList.wait();
63                 }
64                 catch (InterruptedException e)
65                 {
66                     e.printStackTrace();
67                 }
68             }
69             for (int i = num - 1; i >= 0; i--)
70             {
71                 try
72                 {
73                     Thread.sleep(20);
74                 }
75                 catch (InterruptedException e)
76                 {
77                     e.printStackTrace();
78                 }
79                 carList.remove(i);
80             }
81             System.out.println(Thread.currentThread() + ":已經駛出" + num + "輛,目前車庫數目:" + carList.size());
82             carList.notify();
83         }
84     }
85 
86     public List<Car> getCarList()
87     {
88         return carList;
89     }
90 
91     public void setCarList(List<Car> carList)
92     {
93         this.carList = carList;
94     }
95 
96 }      

停車場 CarPark

Java多線程(四) 線程池
Java多線程(四) 線程池
1 package com.scl.thread.threadPool.CarParkManager;
 2 
 3 public class Car
 4 {
 5     private String id;
 6     private String name;
 7 
 8     public Car(String id, String name)
 9     {
10         this.id = id;
11         this.name = name;
12     }
13 
14 }      

汽車資訊類 Car

Java多線程(四) 線程池
Java多線程(四) 線程池
1 package com.scl.thread.threadPool.CarParkManager;
 2 
 3 public class Secure implements Runnable
 4 {
 5     private int driverOutNum;
 6 
 7     private CarPark carPark;
 8 
 9     public Secure(CarPark carPark)
10     {
11         this.carPark = carPark;
12     }
13 
14     public int getDriverOutNum()
15     {
16         return driverOutNum;
17     }
18 
19     public void setDriverOutNum(int driverOutNum)
20     {
21         this.driverOutNum = driverOutNum;
22     }
23 
24     @Override
25     public void run()
26     {
27         carPark.driverOut(driverOutNum);
28     }
29 }      

保安 Secute

Java多線程(四) 線程池
Java多線程(四) 線程池
1 package com.scl.thread.threadPool.CarParkManager;
 2 
 3 import java.util.concurrent.ExecutorService;
 4 import java.util.concurrent.Executors;
 5 
 6 public class Client
 7 {
 8 
 9     public static void main(String[] args)
10     {
11         // 設定車庫最大值
12         CarPark carPark = new CarPark(5);
13         // 建立線程池
14         ExecutorService threadPool = Executors.newCachedThreadPool();
15         // 建立三個持車人
16         CarOwner cw1 = new CarOwner(carPark);
17         CarOwner cw2 = new CarOwner(carPark);
18         CarOwner cw3 = new CarOwner(carPark);
19         // 設定需要駛入的車輛數目
20         cw1.setDriverInNum(3);
21         cw2.setDriverInNum(3);
22         cw3.setDriverInNum(1);
23 
24         // 建立三個保安
25         Secure s1 = new Secure(carPark);
26         Secure s2 = new Secure(carPark);
27         Secure s3 = new Secure(carPark);
28         s1.setDriverOutNum(1);
29         s2.setDriverOutNum(2);
30         s3.setDriverOutNum(1);
31 
32         threadPool.submit(cw1);
33         threadPool.submit(cw2);
34         threadPool.submit(cw3);
35         threadPool.submit(s1);
36         threadPool.submit(s2);
37         threadPool.submit(s3);
38 
39     }
40 
41 }      

用戶端 Client

 補充:很多時候我們都會寫一些demo,然後用JUnit進行測試。多線程用JUnit來測試真的很好用,但是有個很不人性化的就是:線程池裡面的任務都作為一個類似C#所說的工作者線程進行調用。即:當主線程運作完畢,線程池裡面的東西還沒執行完畢整個程式就退出了。讓主線程去等待子線程執行,有幾個方法:①用子線程調用join方法(線程池不适用,本來線程池就是要弱化對線程的操作)②在主線程内用Thread.sleep(10000); 然而我們沒法知道線程池内的任務到底要多久,sleep的數值一直要設定很大 ③找個方法擷取線程池是否把任務執行完畢。毫無疑問第三種方法是最佳的。

  擷取任務是否完成,需要按照以下兩個步驟執行。(假如有線程池threadPool)

  1. 停止往線程池送出任務。調用threadPool.shutdown( ); 

  2. 循環判斷線程池是否空置。調用threadPool.isTerminated( );

      即可在Junit測試方法内,在添加完所有任務後,加上如下代碼:

1         threadPool.shutdown();
 2         while (true)
 3         {
 4             //如果任務已經執行完畢,則跳出循環
 5             if (threadPool.isTerminated())
 6             {
 7                 break;
 8             }
 9             //主線程等待
10             Thread.sleep(1000);
11         }
12         //徹底關閉線程池
13         threadPool.shutdownNow();      

由上述代碼可見,使用線程池和使用Thread類進行送出沒有太大的差異。JDK5提供了一個阻塞隊列的,能夠更好地模拟生産者-消費者模型。後續再進行總結。

以上為線程池的總結内容,如有錯漏煩請指出糾正。

繼續閱讀