java自定义线程池--ThreadPoolExecutors

痛定思痛。 2022-07-14 07:44 306阅读 0赞

ThreadPoolExecutor类简介

在java线程池中的newCachedThreadPool,newFixedThreadPool,newSingleThreadExecutor,newScheduledThreadPool这四个线程池在底层都是调用了ThreadPoolExecutor()这个构造方法。

若Executors这个类无法满足我们的需求的时候,可以自己创建自定义的线程池,ThreadPoolExecutor类的定义如下

  1. public ThreadPoolExecutor(int corePoolSize,//核心线程数--线程池初始化创建的线程数量
  2. int maximumPoolSize,//最大线程数,线程池中能创建的最大线程数
  3. long keepAliveTime,//线程存活时间
  4. TimeUnit unit,//线程存货时间单位
  5. BlockingQueue<Runnable> workQueue,//一个阻塞队列
  6. ThreadFactory threadFactory//拒绝策略
  7. ) {……}

那么在使用这个方法自定义线程池的时候我们要注意些什么呢 ?

这个ThreadPoolExecutor()构造方法,对于队列是什么类型的比较关键,

1.在使用有界队列的时候:

若有新的任务需要执行,如果线程池实际线程数小于corePoolSize核心线程数的时候,则优先创建线程。若大于corePoolSize时,则会将多余的线程存放在队列中,若队列已满,且最请求线程小于maximumPoolSize的情况下,则自定义的线程池会创建新的线程,若队列已满,且最请求线程大于maximumPoolSize的情况下,则执行拒绝策略,或其他自定义方式。

一个小的Demo说明上面的情况

  1. public class MyTask implements Runnable{
  2. private int id;
  3. private String name;
  4. public int getId() {
  5. return id;
  6. }
  7. public void setId(int id) {
  8. this.id = id;
  9. }
  10. public String getName() {
  11. return name;
  12. }
  13. public void setName(String name) {
  14. this.name = name;
  15. }
  16. public MyTask(int id, String name) {
  17. super();
  18. this.id = id;
  19. this.name = name;
  20. }
  21. @Override
  22. public void run() {
  23. try {
  24. System.out.println("run taskId = "+this.id);
  25. Thread.sleep(5000);
  26. } catch (InterruptedException e) {
  27. // TODO Auto-generated catch block
  28. e.printStackTrace();
  29. }
  30. }
  31. @Override
  32. public String toString() {
  33. return Integer.toString(this.id);
  34. }
  35. }

上面创建了一个类实现了Runable()接口

  1. public class MyThreadPool {
  2. public static void main(String[] args) {
  3. /**
  4. * 1.在使用有界队列的时候:若有新的任务需要执行,如果线程池实际线程数小于corePoolSize核心线程数的时候,则优先创建线程。
  5. * 若大于corePoolSize时,则会将多余的线程存放在队列中,
  6. * 若队列已满,且最请求线程小于maximumPoolSize的情况下,则自定义的线程池会创建新的线程,
  7. * 若队列已满,且最请求线程大于maximumPoolSize的情况下,则执行拒绝策略,或其他自定义方式。
  8. */
  9. ThreadPoolExecutor pool = new ThreadPoolExecutor(// 自定义一个线程池
  10. 1, // coreSize
  11. 2, // maxSize
  12. 60, // 60s
  13. TimeUnit.SECONDS, new ArrayBlockingQueue<>(3) // 有界队列,容量是3个
  14. );
  15. MyTask task1 = new MyTask(1, "task1");
  16. MyTask task2 = new MyTask(2, "task2");
  17. MyTask task3 = new MyTask(3, "task3");
  18. MyTask task4 = new MyTask(4, "task4");
  19. MyTask task5 = new MyTask(5, "task5");
  20. MyTask task6 = new MyTask(6, "task6");
  21. /**
  22. * 此处可以一步步打开看执行结果是不是符合上面注释所说的情况。
  23. */
  24. pool.execute(task1);
  25. pool.execute(task2);
  26. pool.execute(task3);
  27. pool.execute(task4);
  28. // pool.execute(task5);
  29. // pool.execute(task6);
  30. pool.shutdown();
  31. }
  32. }

创建了一个自定义的线程池。

此处我们可以把任务一个一个加入到线程池中去验证上述情况看是否正确。

1.放入task1的时候马上执行,然后休眠5s关闭线程池。

2.放入task1和task2的时候我们发现,此时task1先执行,然后task2等待5s执行,然后等待5s线程池调用shutdown()方法关闭。

3.放入3个任务和4个的时候发现都是一次睡眠等待执行,关闭。

4.放入5个的时候我们发现线程1和线程5是同时执行的。然后依次执行task2,3,4.此时我们coreSize是1队列是满的,此时会创建一个线程。因为是5个任务,队列是满的,并且这种情况maxSize还可以创建一个线程执行,所有1和5是同时打印的,然后按照顺序依次执行。

5.放入6个任务的时候我们发现1和5执行完之后task6会抛出个异常不去执行,然后执行2.3.4.

由此可以验证我们上面说的结论。

2.在使用无界队列的时候:

LinkedBlockingQueue与有界队列相比,除非系统资源耗尽,否则无界队列不存在任务入队失败的情况,若系统的线程数小于corePoolSize时,则新建线程执行corePoolSize,当达到corePoolSize后,则把多余的任务放入队列中等待执行若任务的创建和处理的速速差异很大,无界队列会保持快速增长,直到耗尽系统内存为之,对于无界队列的线程池maximumPoolSize并无真实用处。

无界队列这个很好理解,此处就不做代码说明。

3.拒绝策略

jdk给我们提供了一些拒绝策略

1.AbortPolicy:直接抛出异常,系统正常工作。(默认的策略)

2.CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中执行,运行当前被丢弃的任务。

3.DiscardOrderstPolicy:丢弃最老的请求,尝试再次提交当前任务。

4.丢弃无法处理的任务,不给于任何处理。

如果需要自定义策略,需要实现RejectedExecutionHandler接口。

发表评论

表情:
评论列表 (有 0 条评论,306人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java线ThreadPoolExecutor

    为什么使用线程池 如果每次使用线程都创建,每次创建和销毁的开销会很大,线程池主要用来解决线程生命周期开销问题和资源不足问题,也消除了线程创建所带来的延迟。 线