- 从阻塞队列开始说起
- 在操作阻塞队列时,如果队列内容为空,那么消费线程会被阻塞;如果队列已经满了,那么生产线程将会阻塞
- 阻塞队列的分类
- ArrayBlockingQueue
- 有界队列
- 底层为Array形式存储
- 如果所有的任务都是按顺序执行,不存在“插队”和从队伍中离开,则适合使用ArrayBlockingQueue
- LinkedBlockingQueue
- 无界队列
- 底层为链表形式存储
- 如果存在“插队”,和从队伍中离开的情况则适合使用LinkedBlockingQueue
- SynchronousQueue
- 只能有一个元素
- ArrayBlockingQueue
- 阻塞队列的使用示例略
- 使用线程的优势
- 减少了线程创建,销毁的性能消耗,复用了线程
- 充分利用cpu的内核(相对于单线程),省略了上下文的切换
- 提高了线程的可管理性,线程池可以对线程统一分配,调优和监控
- 由于减少了线程的创建时间,所以响应速度会有所提高
- 线程池的几个参数作用
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
- corePoolSize:线程池的核心线程数量,如果线程池中的线程数量小于这个数,每次执行execute()方法会创建新的线程;如果正在运行的线程数大于corePoolSize,当有新的任务时候,会将任务加入到阻塞队列
- 当任务已经填满阻塞对列,则会创建新的线程去执行任务,直到线程数到达maximumPoolSize;maximumPoolSize就是线程池中可以创建的最大线程数;如果线程池中的线程已经超过corePoolSize,当队列中的任务逐渐减少时,线程池的中的线程也会销毁。
- 当线程超过keepAliveTime没有执行任务时,线程池中的线程就会销毁,直到线程数量减少到 corePoolSize;如果设置了allowCoreThreadTimeOut,核心线程也会销毁,线程池中线程数量会减少到0
- unit参数就是线程超时销毁的时间单位,如TimeUnit.MILLISECONDS
- workQueue就是暂存任务的阻塞队列
- threadFactory是创建线程的工厂类,一般使用默认的工厂类,Executors.defaultThreadFactory()
- handler 线程池的拒绝策略
- 在线程池中线程的数量已经达到maximumPoolSize,并且阻塞对列中已经放满了待执行的任务,那么线程池就会执行拒绝策略,拒绝新的任务
- 常见的拒绝策略
- new ThreadPoolExecutor.CallerRunsPolicy()
- 将新的任务交给调用者执行
- new ThreadPoolExecutor.AbortPolicy()
- 直接抛出RejectedExecutionException异常
- new ThreadPoolExecutor.DiscardPolicy()
- 丢弃调新的任务,但不抛出异常
- new ThreadPoolExecutor.DiscardOldestPolicy()
- 丢弃队列中最老的任务,但不抛出异常
- new ThreadPoolExecutor.CallerRunsPolicy()
- Executors提供的几个线程池就像Collectionss提供针对Collection的工具一样,Executors提供了针对Executor的各种工具方法,其中最常见的便是·创建线程的工具方法,有四种:
特别注意:在生产项目中一般不使用Executors提供的这几个线程池,因为它底层的阻塞队列是无限的,有可能会造成OOM//创建n个线程,n为Integer.MAX_VALUE Executors.newCachedThreadPool(); //有固定线程数的线程池,corePoolSize和maxPoolSize相等 Executors.newFixedThreadPool(10); //有定时或周期执行的线程池 Executors.newScheduledThreadPool(10); //只有一个线程的线程池 corePoolSize = maxPoolSize = 1 Executors.newSingleThreadExecutor();
- 带返回值的线程池
- 如果要获取子线程返回的结果,那么要实现Callable接口
- FutureTask类实现了Runnable接口,并且拥有包含Callable参数的构造函数FutureTask(Callable task),可以通过futureTask去执行要获得返回结果的子线程
- 一个执行实现Callable接口的示例
public class ThreadPoolDemo {
public static void main(String[] args){ TaskWithReturn taskWithReturn = new TaskWithReturn(); FutureTask<Integer> taskWithReturnFutureTask = new FutureTask<Integer>(taskWithReturn); Thread thread = new Thread(taskWithReturnFutureTask); thread.start(); } private static class TaskWithReturn implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println(Thread.currentThread().getName()); return 1234; } }
}
- ThreadPoolExecutor的execute方法和submit方法区别
- execute只能执行实现了Runnable接口的类;submit可以执行实现了Runnable接口的类也可以执行实现了Callable接口的类
- submit方法可以通过返回值Future获取到子线程的执行结果和异常信息, 如果线程正确执行完毕future.get()获取到线程的返回结果,如果子线程抛出异常future.get()获取到异常信息
二、 线程池解决的什么问题
线程池解决的核心问题就是资源管理问题。在并发环境下,系统不能够确定在任意时刻中,有多少任务需要执行,有多少资源需要投入。这种不确定性将带来以下若干问题:
- 频繁申请/销毁资源和调度资源,将带来额外的消耗,可能会非常巨大。
- 对资源无限申请缺少抑制手段,易引发系统资源耗尽的风险。
- 系统无法合理管理内部的资源分布,会降低系统的稳定性。
评论区