- 从阻塞队列开始说起
- 在操作阻塞队列时,如果队列内容为空,那么消费线程会被阻塞;如果队列已经满了,那么生产线程将会阻塞
- 阻塞队列的分类
- ArrayBlockingQueue
- 有界队列
- 底层为Array形式存储
- 如果所有的任务都是按顺序执行,不存在“插队”和从队伍中离开,则适合使用ArrayBlockingQueue
- LinkedBlockingQueue
- 无界队列
- 底层为链表形式存储
- 如果存在“插队”,和从队伍中离开的情况则适合使用LinkedBlockingQueue
- SynchronousQueue
- 只能有一个元素
- 阻塞队列的使用示例 略
- 使用线程的优势
- 减少了线程创建,销毁的性能消耗,复用了线程
- 充分利用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()
- 丢弃队列中最老的任务,但不抛出异常
- Executors提供的几个线程池 就像Collectionss提供针对Collection的工具一样,Executors提供了针对Executor的各种工具方法,其中最常见的便是·创建线程的工具方法,有四种:
//创建n个线程,n为Integer.MAX_VALUE
Executors.newCachedThreadPool();
//有固定线程数的线程池,corePoolSize和maxPoolSize相等
Executors.newFixedThreadPool(10);
//有定时或周期执行的线程池
Executors.newScheduledThreadPool(10);
//只有一个线程的线程池 corePoolSize = maxPoolSize = 1
Executors.newSingleThreadExecutor();
特别注意:在生产项目中一般不使用Executors提供的这几个线程池,因为它底层的阻塞队列是无限的,有可能会造成OOM
- 带返回值的线程池
- 如果要获取子线程返回的结果,那么要实现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()获取到异常信息