欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JUC编程——(四)线程池(七大参数,四种拒绝策略,自定义一个线程池,合理设置最大线程数,线程池队列的选择)

程序员文章站 2022-03-08 18:59:46
...

线程池

1、三大方法

public class Demo {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();//1.创建单个线程的线程池
        ExecutorService executorService1 = Executors.newFixedThreadPool(5);//2.创建固定线程数量的线程
        ExecutorService executorService2 = Executors.newCachedThreadPool();//3.根据任务的多少创建线程数量
        try {
            for (int i = 0; i < 100; i++) {
                executorService2.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完之后关闭线程池
            executorService2.shutdown();
        }
    }
}

2、七大参数

我们通过阅读源码可以发现,上面创建线程池的方法底层都是调用ThreadPoolExecutor这个方法进行的,它又七个参数。

public ThreadPoolExecutor(int corePoolSize,//核心线程数
                              int maximumPoolSize,//最大线程数
                              long keepAliveTime,//超时没有人调用会被释放
                              TimeUnit unit,//超时单位
                              BlockingQueue<Runnable> workQueue,//阻塞队列
                              ThreadFactory threadFactory,//线程工厂:创建线程
                              RejectedExecutionHandler handler) //拒绝策略
                              {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

以银行办业务为例解释一下这七大参数。
图1
JUC编程——(四)线程池(七大参数,四种拒绝策略,自定义一个线程池,合理设置最大线程数,线程池队列的选择)
图2
JUC编程——(四)线程池(七大参数,四种拒绝策略,自定义一个线程池,合理设置最大线程数,线程池队列的选择)

3、四种拒绝策略

JUC编程——(四)线程池(七大参数,四种拒绝策略,自定义一个线程池,合理设置最大线程数,线程池队列的选择)

1. new ThreadPoolExecutor.AbortPolicy(),不处理直接抛出异常。java.util.concurrent.RejectedExecutionException。
2. new ThreadPoolExecutor.CallerRunsPolicy(),哪来的去哪去,交由主线程处理。
3. new ThreadPoolExecutor.DiscardPolicy(),直接丢掉任务,不会抛出异常。
4. new ThreadPoolExecutor.DiscardOldestPolicy(),去尝试和线程开启最早的竞争cpu,也不会抛出异常

4、自定义线程池

package com.JUC编程.线程池;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyExecutors {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),//默认的线程创建工厂
                new ThreadPoolExecutor.AbortPolicy());//不处理,直接抛出异常
        try {
            for (int i = 1; i <=9; i++) {
                threadPoolExecutor.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"被使用");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPoolExecutor.shutdown();
        }
    }
}

测试

2个人时:开启了1个线程
5个人时:还是开启了2个线程
6个人时:开启了3个线程
7个人时:开启了4个线程
8个人时:开启了5个线程
9个人时:根据不同的拒绝策略做出反应

5、如何合理的设置最大线程数?

分析下线程池处理的程序是CPU密集型,还是IO密集型

(1)CPU密集型

CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%,CPU要读写I/O(硬盘/内存),I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading很高。
在多重程序系统中,大部分时间用来做计算、逻辑等CPU动作称之为CPU bound,例如一个计算圆周率至小数点一千位以下的程序,在执行的过程当中绝大部分时间用在三角函数和开根号的计算,便是属于CPU bound的程序。
CPU bound的程序一般而言CPU占用率相当高。这可能是因为任务本身不太需要访问I/O设备,也可能是因为程序是多线程实现因此屏蔽掉了等待I/O的时间。

(2)IO密集型

IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU等等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。
I/O bound的程序一般在达到性能极限时,CPU占用率仍然较低。这可能是因为任务本身需要大量I/O操作,而pipeline做得不是很好,没有充分利用处理器能力。

(3)获取电脑的cpu核数
 System.out.println(Runtime.getRuntime().availableProcessors());
(4)设置
  • CPU密集型:CPU的核数是多少,最大线程数设为多少。
  • IO密集型:判断程序中是否耗IO的线程的数量,最大线程数就设置为这个的2倍。

6、线程池队列的选择

当线程数组超过核心线程数时用于保存任务的队列,主要有三种类型的BlockingQueue可供续选择:*队列,有界队列,和同步移交。

(1)*队列

队列的大小无限制,常用的为*的LinkedBlockingQueue,使用该队列作为阻塞队列时尤其要当心,当任务耗时较长时可能会导致大量新任务在队列中堆积导致OOM,newFixedThreadPool采用的就是这个。
当然对于这种队列,最大线程数的值也就无效了,**当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用*队列。**例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许*线程具有增长的可能性。

(2)有界队列

当使用有限的最大线程数时,有界队列有助于防止资源耗尽,但是可能较难调整和控制。常用的有两类,一类是遵循FIFO原则的队列如ArrayBlockingQueue,另一类是优先队列如PriorityBlockingQueue。PriorityBlockingQueue中的优先级由任务的Comparator决定。
使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。

(3)同步队列

如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用*线程池或者有饱和策略时才建议使用该队列。

相关标签: 并发编程