一、Executors线程框架

1)概述

传统方式,通过new Thread来创建对象,具有以下几个特点:
a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
相比new Thread,Java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。

2)Java的四种线程池

Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
若是Executors提供的线程池还不能满足你的使用要求,你可以使用自定义的线程池。
Executors内的线程池其实是公用了一个方法来创建的,可以通过输入具体的参数来生成指定功能的线程池
同样,还可以指定或者自定义具体的线程拒绝策略RejectedExecutionHandler
jdk提供的拒绝策略分别为有:
AbortPolicy直接抛出RejectedExecutionException拒绝异常,程序继续执行
CallerRunsPolicy只要线程池未关闭,直接在调用线程运行当前被丢弃的任务
DiscardOldestPolicy丢弃最老或者说最早插入队列的那个请求,尝试再次提交当前任务
DiscardPolicy丢弃任务,不给予任何处理
通常来说,可以自定义拒绝策略,或者在插入任务之前就打印log或者发放httpClient通知来保证数据的安全。

  1. public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
  2. int maximumPoolSize,//最大线程池大小
  3. long keepAliveTime,//活跃时间
  4. TimeUnit unit,//时间单位
  5. BlockingQueue<Runnable> workQueue,//存放线程的队列
  6. ThreadFactory threadFactory,//线程工厂
  7. RejectedExecutionHandler handler//线程拒绝方式
  8. ){...}

image.gif

3)自定义线程池

1.使用有界队列的自定义线程池

在使用有界队列时,若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程,
若大于corePoolSize,则会将任务加入队列,
若队列已满,则在总线程数不大于maximumPoolSize的前提下,创建新的线程,
若线程数大于maximumPoolSize,则执行拒绝策略,或其他自定义方式。

  1. public class UseThreadPoolExecutor1 {
  2. public static void main(String[] args) {
  3. ThreadPoolExecutor pool = new ThreadPoolExecutor(
  4. 1, //coreSize
  5. 2, //MaxSize
  6. 60, //60
  7. TimeUnit.SECONDS,
  8. new ArrayBlockingQueue<Runnable>(3) //指定一种队列 (有界队列)
  9. );
  10. MyTask mt1 = new MyTask(1, "任务1");
  11. MyTask mt2 = new MyTask(2, "任务2");
  12. MyTask mt3 = new MyTask(3, "任务3");
  13. MyTask mt4 = new MyTask(4, "任务4");
  14. MyTask mt5 = new MyTask(5, "任务5");
  15. MyTask mt6 = new MyTask(6, "任务6");
  16. pool.execute(mt1);
  17. pool.execute(mt2);
  18. pool.execute(mt3);
  19. pool.execute(mt4);
  20. pool.execute(mt5);
  21. pool.execute(mt6);
  22. pool.shutdown();
  23. }
  24. }

image.gif
插入了6个任务,创建线程执行任务1,234插入队列,然后创建线程执行任务5,然后2个线程依次执行队列中的任务。
默认采用AbortPolicy拒绝策略,程序抛出异常后继续执行
执行结果:
JAVA的并发编程(七):Java的四种线程池和自定义线程池 - 图3image.gif

2.使用无界队列的自定义线程池

在使用有界队列时,若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程,
若大于corePoolSize,则会将任务加入队列。
如果任务创建和处理的速度差异很大,无界队列会保持快速增长,直到出现oom内存溢出。

  1. public class UseThreadPoolExecutor2 implements Runnable{
  2. private static AtomicInteger count = new AtomicInteger(0);
  3. @Override
  4. public void run() {
  5. try {
  6. int temp = count.incrementAndGet();
  7. System.out.println("任务" + temp);
  8. Thread.sleep(2000);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. public static void main(String[] args) throws Exception{
  14. BlockingQueue<Runnable> queue =
  15. new LinkedBlockingQueue<Runnable>();
  16. ExecutorService executor = new ThreadPoolExecutor(
  17. 2, //core
  18. 10, //max
  19. 120L, //2fenzhong
  20. TimeUnit.SECONDS,
  21. queue);
  22. for(int i = 0 ; i < 20; i++){
  23. executor.execute(new UseThreadPoolExecutor2());
  24. }
  25. Thread.sleep(1000);
  26. System.out.println("queue size:" + queue.size()); //10
  27. Thread.sleep(2000);
  28. }
  29. }

image.gif
插入20个任务,由于corePoolSize核心线程数为2,所以执行2个任务,其他18个任务存入队列依次执行
结果如下:
JAVA的并发编程(七):Java的四种线程池和自定义线程池 - 图6image.gif