线程池ThreadPoolExecutor

线程池简介

JAVA在jdk1.5后并发包中提供线程池

线程池作用

1:降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
2:提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行;
3:方便线程并发数的管控,线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或OOM等状况,从而降低系统的稳定性。线程池能有效管控线程,统一分配、调优,提供资源使用率;
4:更强大的功能,线程池提供了定时、定期以及可控线程数等功能的线程池,使用方便简单。

线程池的创建

jdk提供的有工具类Executors创建常见的线程池

1:newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
2:newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 可伸缩线程池最常用
3:newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
4:newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

可以通过这些静态方法直接创建线程池,但是不建议(甚至是不允许,阿里巴巴开发手册里面严禁声明禁止使用Executors创建线程池——会在成OOM问题)如下

public class OOMTest {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(12);

        for (int i = 1; i < Integer.MAX_VALUE; i ++) {
            executorService.execute(new RunnableTask());
        }
    }
}
class RunnableTask implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "添加主任务成功!!");
        try {
            Thread.sleep(10000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行main方法 通过jvm参数指定-Xmx8m -Xms8m 详情参考jvm参数大全 意思分配最大堆,最小堆8M内存 从而快速造成JVM内存溢出问题  启动后发现结果如下

因为底层指定工作队列为LinkedBlockingQueu<Runnable>初始大小也是为Integer.MAX_VALUE大小就是无边界阻塞队列 而代码中不断的向队列中添加任务,而JVM运行时指定最大堆、最小堆为8M,这时候就因任务过多而导致内存溢出等问题。所以建议使用ThreadPoolExecutor创建线程池,可使用下面方式创建其实以上方法也只是针对构造ThreadPoolExecutor简单的封装而已

  • corePoolSize:线程池的核心线程数,线程池中运行的线程数也永远不会超过 corePoolSize 个,默认情况下可以一直存活。可以通过设置allowCoreThreadTimeOut为True,此时 核心线程数就是0,此时keepAliveTime控制所有线程的超时时间。
  • maximumPoolSize:线程池允许的最大线程数;
  • keepAliveTime: 指的是空闲线程结束的超时时间;
  • unit :是一个枚举,表示 keepAliveTime 的单位;
  • workQueue:表示存放任务的BlockingQueue<Runnable队列。
  • BlockingQueue:阻塞队列(BlockingQueue)是java.util.concurrent下的主要用来控制线程同步的工具。如果BlockQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤醒。同样,如果BlockingQueue是满的,任何试图往里存东西的操作也会被阻断进入等待状态,直到BlockingQueue里有空间才会被唤醒继续操作。
    阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。具体的实现类有LinkedBlockingQueue,ArrayBlockingQueued等。一般其内部的都是通过Lock和Condition(显示锁(Lock)及Condition的学习与使用)来实现阻塞和唤醒。

线程池的运行过程

线程池使用submit及execute执行任务

  • 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
  • 当调用 execute() 方法添加一个任务时,线程池会做如下判断:

1:如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
2:如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
3:如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
4:如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常RejectExecutionException。
5:当一个线程完成任务时,它会从队列中取下一个任务来执行。

  • 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

发表评论

电子邮件地址不会被公开。 必填项已用*标注