一个优秀的软件不会随意的创建、销毁线程,因为创建和销毁线程需要耗费大量的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( ); 创建一个定长线程池,定时或周期性地执行任务
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)。不同的持车人把车驶入到车库,保安负责记录车辆离开车库的数量。车库容量固定,如果车库为空保安可以通知持车人把车驶入车库。如果车库满了,保安告知持车人等待另外的持车人把车驾驶出来。车库使用一个列表对入库的车信息进行记录。
因此有如下类图:
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
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
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
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
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提供了一个阻塞队列的,能够更好地模拟生产者-消费者模型。后续再进行总结。