JAVA线程池的理解

本文详细介绍了线程池的概念,如何通过线程池提高异步任务执行效率,以及关键参数如核心线程数、最大线程数、空闲线程存活时间和工作队列的选择。通过对比不使用线程池和使用线程池的示例,展示了线程池的优势。此外,还列出了线程池的主要配置和执行流程,帮助读者理解如何根据任务类型选择合适的线程池类型。
摘要由CSDN通过智能技术生成

线程池简介

       当我们需要执行异步任务时,可以选择new一个新的线程来运行,但线程的创建和销毁是需要开销的,执行大量异步任务时这样做会对系统性能产生影响。使用线程池可以很好的解决这样的问题,线程池里面的线程是可以复用的,不用每次执行异步任务时都重新创建和销毁线程。线程池还有一个优势就是提供了一种资源限制手段和管理手段,比如可以限制线程的个数。我们可以通过一个实例来看下两者之间的差异

不使用线程池:

public static void main(String[] args) throws InterruptedException {
    long t1 = System.currentTimeMillis();
    AtomicLong atomicLong = new AtomicLong(0L);
    Thread[] threads = new Thread[1000000];
    for (int i = 0; i < threads.length; i++) {
        threads[i] = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(atomicLong.incrementAndGet());
            }
        });
        threads[i].start();
    }
    for (Thread thread : threads) {
        thread.join();
    }
    long t2 = System.currentTimeMillis();
    System.out.println("不使用线程池耗时:" + (t2 - t1));
}
不使用线程池耗时:67769

使用线程池:

public static void main(String[] args) throws ExecutionException, InterruptedException {
   long t1 = System.currentTimeMillis();
    AtomicLong atomicLong = new AtomicLong(0L);
    ExecutorService executorService = Executors.newFixedThreadPool(8);
    Future[] futures = new Future[1000000];
    for (int i = 0; i < 1000000; i++) {
        futures[i] = executorService.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                return atomicLong.incrementAndGet();
            }
        });
    }
    for (Future future : futures) {
        System.out.println(future.get());
    }
    executorService.shutdown();
    long t2 = System.currentTimeMillis();
    System.out.println("使用线程池耗时:" + (t2 - t1));
}
使用线程池耗时:9789

线程池主要参数

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
}

1.corePoolSize(线程池核心线程大小)

       当向线程池提交任务时,如果已有线程数量少于corePoolSize时不管是否存在空闲线程,都会创建新的线程,直到已有线程数量大于或等于corePoolSize;并且线程池会维护一个最小线程数量为corePoolSize,即使这些线程处于空闲状态也不会被销毁,除非设置了allowCoreThreadTimeOut

2.maximumPoolSize(线程池最大线程数量)

       线程池所允许的最大线程个数。当向线程池提交任务时,首先会去寻找空闲线程,如果有则直接执行,如果没有则会加入工作队列中,当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数

3.keepAliveTime(空闲线程存活时间)

       线程池中的线程数大于核心线程数corePoolSize时,如果存在空闲线程,在指定时间(keepAliveTime)后,这个线程就会被销毁

4.unit(空闲线程存活时间单位)

keepAliveTime的时间单位

5.workQueue(工作队列)

       当向线程池提交任务时,首先会去寻找空闲线程,如果没有则会加入工作队列中,任务调度时再从队列中取出任务。在ThreadPoolExecutor的构造函数中可使用以下几种BlockingQueue

ArrayBlockingQueue

基于数组的有界阻塞队列,按照FIFO排序。使用ArrayBlockingQueue时,若有新任务加入,且当前线程数大于核心线程数时则会加入工作队列。若工作队列已满,在当前线程数小于maximumPoolSize时会创建新的线程执行任务,若大于maximumPoolSize则会执行拒绝策略。

LinkedBlockingQueue

基于链表的无界阻塞队列(实际上最大容量为Interger.MAX_VALUE),按照FIFO排序。使用LinkedBlockingQueue时,若有新任务加入,且当前线程数大于核心线程数时会加入工作队列。但是,除非系统资源耗尽,否则无界队列不会存在入队失败的情况,所以对于无界队列,maximumPoolSize参数会失效。当任务处理速度和创建速度不匹配时,无界队列会快速增长,很快耗尽系统内存。

SynchronousQuene

直接提交的队列,即不缓存任务的阻塞队列。也就是说,新任务加入时不会被保存,而是直接加入调度,如果没有空闲的线程则直接创建新的线程执行任务,若线程数大于maximumPoolSize则执行拒绝策略。

PriorityBlockingQueue

具有优先级的无界阻塞队列,优先级通过参数comparator实现

6.threadFactory(创建线程的工厂)

用于创建新线程的工厂,可以用来设定线程名、是否为daemon线程等等

7.handler(线程拒绝策略)

当线程池线程数量达到maximumPoolSize并且队列也满了,再加入线程时会执行此策略。jdk提供了以下4种拒绝策略

AbortPolicy

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    throw new RejectedExecutionException("Task " + r.toString() +
                                         " rejected from " +
                                         e.toString());
}

直接丢弃任务,并抛出RejectedExecutionException异常

CallerRunsPolicy

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        r.run();
    }
}

如果线程池没有shutdown,则直接使用调用线程执行任务,否则直接丢弃任务

DiscardPolicy

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}

直接丢弃任务,什么也不做

DiscardOldestPolicy

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        e.getQueue().poll();
        e.execute(r);
    }
}

如果线程池没有shutdown,则丢弃最先进入队列的任务,尝试把当前任务加入到队列中

线程池执行流程

线程池执行流程图

1.线程池初始化的时候里面是没有线程的,工作队列是以参数的形式传进来的,而且就算工作队列里面有任务也不会马上去执行

2.当调用execute() 方法添加任务时会做如下判断

  • 如果当前线程数小于核心线程数,则新建线程执行任务
  • 如果当前线程数大于核心线程数,则将任务加入工作队列
  • 如果工作队列满了,且当前线程数小于最大线程数,则新建线程执行任务
  • 如果工作队列满了,且当前线程数大于最大线程数,则执行拒绝策略

3.当一个线程完成任务时,会从工作队列中取一个任务来执行

4.当一个线程空闲时,会先判断当前线程数大于是否大于核心线程数,如果当前线程数大于核心线程数,那么此线程会在存活一定时间(keepAliveTime)后销毁,最后线程池中的线程数会保持在corePoolSize的大小

线程池配置

CPU密集型

CPU密集型,就是指CPU使用率很高,如果线程池的线程数量过多,那么会导致CPU不断的切换线程,建议线程池数量设置为CPU核心数+1

I/O密集型

I/O密集型,就是CPU使用率不高,因此可以让CPU在等待I/O的时候去处理更多的任务,充分利用CPU,建议线程池数量设置为2*CPU核心数

四种线程池

       线程池提供了许多可调参数和可扩张性接口,以满足不同情景的需求,让开发人员能够更方便的使用Executors的方法。

1.newFixedThreadPool

       创建一个固定大小的线程池,可控制线程最大并发数,超出的线程会在队列中等待

2.newSingleThreadExecutor

       创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO)执行

3.newCachedThreadPool

       创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程(线程池线程个数最多可达Integer.MAX_VALUE )

4.newScheduledThreadPool

       创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值