一、线程池适用场景
线程池技术适用于处理并发的大量短连接请求,可以免去线程创建和销毁时损耗的系统资源,这也是所有“池”的目的。开发者可以实现满足自己需求的线程池,在本节结尾处作者会分享一个简单线程池的实现来一起深入学习线程池。
二、java內建线程池
从java5开始,新增了一个Executors工厂类来产生线程池(所谓工厂类就是不同与使用new 构造器 这种方式新建对象,而是使用getXXX来隐藏掉具体对象的创建与赋值过程,像工厂一样提供对象成品,隐藏制造过程),该工厂类包涵一下静态方法来创建线程池:
ExcutorService newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将被缓存在线程池中(内存中)。
ExcutorService newFixedThreadPool(int num) : 可重用的,具有固定线程数的线程池。
ExcutorService newSingleThreadExecutor():创建一个只有单线程的线程池,相当于调用newFixThreadPool(1)。
ScheduledExecutorService newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,可以在指定延迟后执行线程任务,即使线城市空闲的也会被保存在内存中。
ScheduledExecutorService newSingleThreadScheduleExecutor():只有一个线程的线程池,可以再指定延迟后执行线程任务。
ExcutorService newWorkStealingPool(int parallelism):创建持有足够线程的线程池来支持给定的并行级别,该方法还会使用多个队列来减少竞争。
ExecutorService newWorkStealingPool():如果当前机器有4个CPU,则目标并行级别被设置为4,相当于为前一个方法传入4作为参数。
前三个方法返回代表线程池的对象,中间两个返回ScheduledExecutorService 是ExcutorService 的子类,可以再指定延迟后执行线程任务;最后两个方法是java8的,这两个方法生成的work stealing池相当于后台线程池。
下面介绍一下线程池类 ExecutorService提供的提交任务方法,ExecutorService代表尽快执行的线程池
Future<?> submit(Runnable task) :将一个Runnable对象提交给指定线程池,线程池会在会在有空闲线程时执行Runnable对象代表的任务,返回一个Future实现类,通过该实现类可以取得返回值,但是run方法没有返回值,run方法执行结束后 返回一个null的Future,但可以调用Future的isDone()、isCancelled()方法来获得Runnable对象的执行状态
<T> Future<T> submit(Runnable task , T result):result显示指定线程执行结束后的返回值,所以Future对象将在run方法执结束后返回result
<T> Future<T> submit(Callable<T> task) :将一个Callable对象作为任务提交,Future代表Callable的call()方法的返回值
ScheduledExecutorService类提供的服务就更加丰富了,还包含定时任务,代表可在指定延迟后或周期性的执行线程任务的线程池
在线程池使用完毕后,应该调用该线程池的shutdown()方法,该方法将启动线程池的关闭序列,线程池不再接受任务但是会把已有的任务完成,当线程池中所有任务都执行完毕之后,池中所有线程都会死亡。还有一个shutdownNow方法,调用该方法则关闭线程池,试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
三、java8 增强的ForkJoinPool
java7开始,提供了ForkJoinPool来支持一个任务拆分成多个小任务做并行计算,再把多个小任务的结果合并成总的计算结果,ForkJoinPool是ExecutorService的实现类。java8为ForkJoinPool增加了通用池的功能。详细的轮子使用方法不再赘述
四、线程相关类
ThreadLocal类,该类用于将线程共享的变量复制多份,为每个线程单独保存一份变量的副本。
Collections类,可以把线程不安全的集合类包装成线程安全集合类。
以Concurrent开头的集合类,代表支持并发访问的集合。但是这类集合读写操作没有使用锁,而是用了更复杂的算法(。。用到了再查,或者用到了直接看看源码)
CopyOnWriteArray、CopyOnWriteArraySet。线程安全的集合,在进行写操作时会copy一份副本,而读操作却是读取原集合。
五、实现自己的线程池
由上面的java內建线程池的学习,可以了解到线程池在使用前需要创建,使用后需要销毁,使用中需要传入任务,所以至少需要创建、提交任务、销毁这三个public方法;线程池中还需要有工作线程;线程池中还要有工作队列来存放没有处理的任务,作为一种缓冲。
公认的线程池四大要素:
- 线程池管理器(ThreadPoolManager):用于创建并管理线程池
- 工作线程(WorkThread): 线程池中线程
- 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行。
- 任务队列:用于存放没有处理的任务。提供一种缓冲机制。
我们的任务接口就设定为Runnable接口,所以不需要再设计任务接口。
任务队列:LinkedBlockingDeque<Runnable> taskList
工作线程:WorkThread[] workThreads
package ThreadPoolTest; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.atomic.AtomicBoolean; public class ShyThreadPool { private static ShyThreadPool instance = null; //缓存系统线程池 private AtomicBoolean isShutDown = new AtomicBoolean(false); private LinkedBlockingDeque<Runnable> taskList = new LinkedBlockingDeque<>(); //任务队列,使用阻塞队列,支持多线程 private static int maxparallelism = 0; //最大并发线程数量 private static int parallelism = 0; //线程池工作线程数量,初始时为0 private WorkThread[] workThreads; //工作线程数组 //getter public int getParallelism() { return parallelism; } public static int getMaxparallelism() { return maxparallelism; } //提供设置最大并发线程数量的构造器,由于线程池在程序中是唯一的,所以设计为单例模式,隐藏构造器 private ShyThreadPool(int maxparallelism) { this.maxparallelism = maxparallelism; workThreads = new WorkThread[maxparallelism]; for(int i = 0 ; i < maxparallelism;i++){ //新建并启动线程 workThreads[i] = new WorkThread(i); workThreads[i].start(); } } //静态工厂方法,返回单例的线程池,并且设计为线程安全的线程池资源(防止多个线程并发调用该方法时new 多个线程池) public static synchronized ShyThreadPool getInstance(int maxparallelism){ if(maxparallelism <= 0){ throw new RuntimeException("ShyThreadPool最大并发线程数量不允许小于等于0"); } if(instance == null) instance = new ShyThreadPool(maxparallelism); return instance; } //以上就是新建线程池的部分 //------------------------------------------------------------------ //----------------------下面是线程池任务提交------------------------- //由于不同线程都有可能对线程池的任务队列进行操作,所以为了支持并发访问该线程池,可以把任务队列作为同步监视器 //使用阻塞队列进行线程间通信(阻塞队列实现了生产者消费者问题),免去使用传统的sychronized+wait+notifyAll方式 public void submit(Runnable task){ if(!isShutDown.get()) { try { taskList.put(task); System.out.println("task submit!"); } catch (InterruptedException ine) { ine.printStackTrace(); } } } public void BatchSubmit(Runnable[] tasks){ for (Runnable task:tasks ) { submit(task); } } //---------------------关闭线程池------------------------------------ //主线程关闭线程池,线程池不能再接受新任务、线程池任务队列中已有的任务会继续进行、当取出任务队列中的元素后任务队列size为0时,结束所有线程,释放资源。 //关闭线程池时要检查任务队列是否为空,若为空则 public void shutdown(){ isShutDown.set(true); while (isShutDown.get()){ if(taskList.size() == 0){ //释放线程池资源 for(WorkThread workThread : workThreads){ workThread = null; } workThreads = null; taskList.clear(); taskList = null; instance = null; isShutDown = null; System.gc(); } } } private class WorkThread extends Thread{ private int taskId; //当前线程执行的任务id private boolean isRunning = true; public WorkThread(int taskId){ //默认调用super(); this.taskId = taskId; } public WorkThread(int taskId,String name){ super(name); this.taskId = taskId; } public boolean isRunning(){ return isRunning; } public void stopThread(){ isRunning = false; } @Override public void run() { //用循环来不断检查 while (isRunning){ try{ Runnable task = taskList.take(); task.run(); if(isShutDown.get() && taskList.size() == 0) stopThread(); }catch (InterruptedException ine){ ine.printStackTrace(); } } } } }