一文简单理解Java线程池的问题

Posted 暴躁的程序猿啊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文简单理解Java线程池的问题相关的知识,希望对你有一定的参考价值。

线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核充分利用,还能防止过分调度。
线程池架构

好处:

降低资源的消耗,提高响应速度, 方便管理

线程复用、可以控制最大并发数、管理线程

特征:

线程池中的线程有一定的量 可以控制并发量
线程可以重复使用 在关闭之前都可以一直使用
超过一定量的线程提交执行时需要在队列中等待

架构

java通过Executor框架实现的 该框架中用到了Executor,Executors,ExecutorService

ThreadPoolExecutor这几个类

在这里插入图片描述

线程池:三大方法,七大参数,四种拒绝策略

三大方法:

//单个线程 只有一个线程
Executors.newSingleThreadExecutor()
 //创建固定大小的线程池   
Executors.newFixedThreadPool(6)
//创建一个可以伸缩的线程池
Executors.newCachedThreadPool()

newFixedThreadPool使用示例

创建一个有6个线程的线程池 10条线程进行请求

线程当执行了execute()方法时线程才会创建

public class Demo1 {
    public static void main(String[] args) {
        //单个线程
//        ExecutorService pool = Executors.newSingleThreadExecutor();
        //创建固定大小的线程池
        ExecutorService pool = Executors.newFixedThreadPool(6);
        //创建一个可以伸缩的线程池
//        ExecutorService pool = Executors.newCachedThreadPool();
        try {
            for (int i = 0; i < 10; i++) {
                pool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "OK");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完要关闭
            pool.shutdown();
        }
    }
}

最多只有六个线程 因为我们指定了6个
在这里插入图片描述
newSingleThreadExecutor使用示例

public class Demo1 {
    public static void main(String[] args) {
        //单个线程
        ExecutorService pool = Executors.newSingleThreadExecutor();
        //创建固定大小的线程池
//        ExecutorService pool = Executors.newFixedThreadPool(6);
        //创建一个可以伸缩的线程池
//        ExecutorService pool = Executors.newCachedThreadPool();
        try {
            for (int i = 0; i < 10; i++) {
                pool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "OK");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完要关闭
            pool.shutdown();
        }
    }
}

只有一个线程
在这里插入图片描述
newCachedThreadPool使用示例

public class Demo1 {
    public static void main(String[] args) {
        //单个线程
//        ExecutorService pool = Executors.newSingleThreadExecutor();
        //创建固定大小的线程池
//        ExecutorService pool = Executors.newFixedThreadPool(6);
        //创建一个可以伸缩的线程池
        ExecutorService pool = Executors.newCachedThreadPool();
        try {
            for (int i = 0; i < 10; i++) {
                pool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "OK");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完要关闭
            pool.shutdown();
        }
    }
}

可以看到 产生了七个线程
在这里插入图片描述

但是根据阿里巴巴开发手册规定 不应该使用Executors创建线程 会产生问题
在这里插入图片描述

所以我们应该使用ThreadPoolExecutor创建线程池 而且我们通过源码可以发现 以上的方式还是调用的ThreadPoolExecutor创建

源码

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

我们可以看到ThreadPoolExecutor源码中定义了七个参数

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

下面我们详细分析一下这七个参数

七大参数

 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;
    }

int corePoolSize, 核心线程池大小 相当于银行的办理业务窗口 肯定有两个窗口是一直开放的

int maximumPoolSize, 最大核心线程池大小 银行的窗口 一共有10个窗口 有两个是一直开放的 其他的在银行忙的时候会开放

long keepAliveTime, 空闲存活时间 其他的窗口开放了 办理业务也完成了 其他开放的窗口没有人办理业务了 等待多长时间没人来办理就会关闭 只留下一直开放的窗口
超时没有人调用就会释放
TimeUnit unit, 时间的单位

BlockingQueue workQueue, 阻塞队列 相当于办理业务时等待叫号的候客厅

ThreadFactory threadFactory, 线程工厂
创建线程的 不用动

RejectedExecutionHandler handler 拒绝策略 当银行的所有窗口都满了 候客厅也满了 就会拒绝为新来的客户办理业务
拒绝策略有四种 稍后会详细分析

使用示例

public class ThreadPoolExecutorDemo {
    public static void main(String[] args) {
        //自定义线程池
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2,
                5,
                //空闲存活时间  三秒钟没有人来这个窗口办理业务  额外的窗口就会关闭
                3,
                TimeUnit.SECONDS,
                //相当于候客区  候客区满了  就会同时开启五条线程
                new ArrayBlockingQueue<>(3),
                //默认的线程工厂不用动
                Executors.defaultThreadFactory(),
                //拒绝策略  银行满了还有人进来,就不处理这个人的业务 抛出异常
                new ThreadPoolExecutor.DiscardOldestPolicy()
                );

        try{
            for (int i = 0; i < 9 ; i++) {
                //最大并发数  最大线程池大小+队列大小
                poolExecutor.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"ok");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
                poolExecutor.shutdown();
        }
    }
}

在这里插入图片描述

小细节:

线程执行了execute()方法时才创建的

加入指定了一个核心为3个线程最大线程为5 阻塞队列为2的线程池

那么前三个线程处理业务时 来的第四个跟第五个 线程会进入阻塞队列等待

来的第六个线程会 开启第四个窗口来优先处理第六个线程

四大拒绝策略

在这里插入图片描述
new ThreadPoolExecutor.AbortPolicy() 丢弃任务 并抛出RejectedExecutionException异常 【 默认 】

最大处理线程数满了就不处理新来的线程并抛出异常

public class ThreadPoolExecutorDemo2 {
    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 = 0; i < 9; i++) {
                //获取线程
                threadPoolExecutor.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"办理业务");
                });
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            //归还线程
            threadPoolExecutor.shutdown();
        }



    }
}

在这里插入图片描述

new ThreadPoolExecutor.CallerRunsPolicy() 由调用线程处理该任务 会用main线程

public class ThreadPoolExecutorDemo2 {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,
                 //最大线程数
                5,
                 //存活时间
                3,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                //线程工厂
                Executors.defaultThreadFactory(),
                //拒绝策略
                new ThreadPoolExecutor.CallerRunsPolicy()
                );
        try{
            for (int i = 0; i < 9; i++) {
                //获取线程
                threadPoolExecutor.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"办理业务");
                });
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            //归还线程
            threadPoolExecutor.shutdown();
        }



    }
}

在这里插入图片描述

new ThreadPoolExecutor.DiscardPolicy() 队列满了就会丢掉任务不会抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy() 丢弃队列最前面的任务,然后重新尝试执行任务 不会抛出异常 如果没竞争到也会丢掉任务

public class ThreadPoolExecutorDemo2 {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,
                 //最大线程数
                5,
                 //存活时间
                3,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                //线程工厂
                Executors.defaultThreadFactory(),
                //拒绝策略
                new ThreadPoolExecutor.DiscardPolicy()
                );
        try{
            for (int i = 0; i < 9; i++) {
                //获取线程
                threadPoolExecutor.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"办理业务");
                });
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            //归还线程
            threadPoolExecutor.shutdown();
        }



    }
}

多出的线程任务被丢弃了
在这里插入图片描述

以上是关于一文简单理解Java线程池的问题的主要内容,如果未能解决你的问题,请参考以下文章

一文读懂线程池的工作原理(故事白话文)

深入理解 Java 线程池的实现原理

java中线程池的理解和使用

Java——线程池

Java线程池详解

Java线程池详解