深挖java线程池

72 阅读6分钟

目录

一、线程池

1.简单介绍

2.几种线程池

3.实际使用哪个线程池

二、线程池的七大参数

三、线程池底层工作原理

四、拒绝策略

1.什么是拒绝策略

2.都有哪几种jdk默认的拒绝策略。

五、手写线程池代码

六、配置线程池

1.CPU密集型


一、线程池

1.简单介绍

线程池的优势:

    线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。

    他的主要特点为:线程复用;控制最大并发数;管理线程。

    第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

    第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

    第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

    Java中的线程池是通过Executor框架实现的,该框架用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类。

2.几种线程池

Executors.newScheduledThreadPool() :带有定时任务的线程池

java8新出的Executors.newWorkStealingPool(int) :java8新增,使用目前机器上可用的处理器作为它的并行级别。

Executors.newFixedThreadPool(int) :执行长期的任务,性能好很多。

Executors.newSingleThreadExecutor() :一个任务一个任务执行的场景。

Executors.newCachedThreadPool() :适用:执行很多短期异步的小程序或者负载较轻的服务器。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyThreadPoolDemo {

    public static void main(String[] args) {
        // 一池5个处理线程
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        ExecutorService executorService2 = Executors.newCachedThreadPool();
        ExecutorService executorService3 = Executors.newSingleThreadExecutor();

        try {
            // execute无返回值,submit有返回值
            executorService.execute(() ->{
                System.out.println("execute");
            });// 可传Runnable

            // executorService.submit(); // 有返回值的

        } catch (Exception e){
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }
}

3.实际使用哪个线程池

    一个默认的都不用,生产上只能使用自定义的!以下是阿里巴巴java开发手册中的内容:

二、线程池的七大参数

七大参数——最终的构造方法:

1.corePoolSize:线程池中的常驻核心线程数。

    在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行任务,近似理解为今日当值线程。

    当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。

2.maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1。

3.keepAliveTime:多余的空闲线程的存活时间。

    当前线程池线程数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止。

4.unit:keepAliveTime的单位。

5.workQueue:任务队列,被提交但尚未被执行的任务。

6.threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程,一般用默认的即可。

7.handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝。

三、线程池底层工作原理

四、拒绝策略

1.什么是拒绝策略

    等待队列也已经满了,再也塞不下新任务了,同时,线程池中的max线程也达到了,无法继续为新任务服务。

    这时候我们就需要拒绝策略机制合理的处理这个问题。

2.都有哪几种jdk默认的拒绝策略。

    AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行。

    CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务流量。

    DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。

    DiscardPolicy:直接丢弃任务,不做任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。

五、手写线程池代码

注意点:

①.如果队列满了,再添加线程时,会开启新线程,队列中的线程还是等待,后来进来的线程会先执行。

import java.util.concurrent.*;

public class MyThreadPoolWriteMySelf {
    public static void main(String[] args) {
        ExecutorService executor = new ThreadPoolExecutor(
                2,
                5,
                1L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
                );


        try{
            // 设置启动的线程数,观察线程池执行任务的变化。
            for (int i = 0; i < 9; i++) {
                final int a = i;
                executor.execute(() ->{
                    System.out.println("开始处理" + a + Thread.currentThread().getName());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            executor.shutdown();
        }
    }
}

六、配置线程池

1.CPU密集型

// 获取CPU核心数

System.out.println(Runtime.getRuntime().availableProcessors());

    CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。

    CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程)。

    而在单核CPU上,无论你开几个模拟的多线程该任务都不可能得到加速,因为CPU总的运算能力就那些

    CPU密集型任务配置尽可能少的线程数量:一般公式:CPU核数+1个线程的线程池。

2.IO密集型(分两种情况)

①由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数*2。

②IO密集型,即该任务需要大量的IO,即大量的阻塞。

    在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。

    所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。

    IO密集型时,大部分线程都阻塞,故需要多配制线程数:

    参考公式:CPU核数/1-阻塞系数(阻塞系数在0.8~0.9之间)

    比如8核CPU:8/1-0.9=80个线程数。