线程池的介绍

线程池的优势

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

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

3、提高线程的可管理性。线程是稀缺资源,如果无限的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配。

总结:

线程复用、控制并发数、管理线程

线程池的使用

数组 Array 数组工具类:Arrays

集合 Collection 集合工具类:Collections

线程 Executor 线程工具类:Executors

线程池的常见三种使用

Executors.newFixedThreadPool(); 创建一个固定大小的线程池

Executors.newSingleThreadExecutor(); 创建单一的线程池

Executors.newCachedThreadPool(); 创建一个带有缓冲的线程池可以自动扩容,用于负载较轻的服务器。

代码实现:

public static void main(String[] args) {
        //创建一个固定大小的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        try{
            for(int i=1;i<=10;i++){ //模仿10次请求
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName() + "\t"+"办理业务");
                    }
                });
            }
        }finally{
            executorService.shutdown(); //用完归还给线程池
        }

    }
结果:
pool-1-thread-1	办理业务
pool-1-thread-1	办理业务
pool-1-thread-1	办理业务
pool-1-thread-1	办理业务
pool-1-thread-1	办理业务
pool-1-thread-1	办理业务
pool-1-thread-2	办理业务
pool-1-thread-3	办理业务
pool-1-thread-4	办理业务
pool-1-thread-5	办理业务

总结:总共就5个线程,是个用户请求,处理完之后还给线程池,可以复用。

单一线程池代码实现:

Executors.newSingleThreadExecutor();

 public static void main(String[] args) {
        //创建一个单一线程的线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        try{
            for(int i=1;i<=5;i++){ //模仿5次请求
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName() + "\t"+"办理业务");
                    }
                });
            }
        }finally{
            executorService.shutdown(); //用完归还给线程池
        }

    }

结果:
pool-1-thread-1	办理业务
pool-1-thread-1	办理业务
pool-1-thread-1	办理业务
pool-1-thread-1	办理业务
pool-1-thread-1	办理业务

总结:线程池中只有一个线程。

随着业务量增长可以实现扩容的线程池代码实现:

Executors.newCachedThreadPool();

public static void main(String[] args) {
        //创建一个自动扩容的线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        try{
            for(int i=1;i<=10;i++){ //模仿10次请求
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName() + "\t"+"办理业务");
                    }
                });
            }
        }finally{
            executorService.shutdown(); //用完归还给线程池
        }

    }
结果:
pool-1-thread-1	办理业务
pool-1-thread-2	办理业务
pool-1-thread-3	办理业务
pool-1-thread-5	办理业务
pool-1-thread-4	办理业务
pool-1-thread-6	办理业务
pool-1-thread-7	办理业务
pool-1-thread-8	办理业务
pool-1-thread-10办理业务
pool-1-thread-9	办理业务

线程池底层源码分析

其实这三个方法创建线程池,点进去源码其实都是创建一个ThreadPoolExecutor对象。这个对象构造里面可以传7个参数。

如图:
image.png

七个参数分析

int corePoolSize :线程池中的常驻核心线程数

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

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

TimeUnit unit : 存活时间单位

BlockingQueue workQueue : 阻塞队列,被提交但未被执行的任务

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

RejectedExecutionHandler handler : 拒绝策略,表示当队列满了,并且工作线程数达到线程池最大线程数,会触发(4中拒绝策略)。

线程池的工作原理:

image.png

分析:

线程池执行execute()方法开启任务,

1、首先由两个核心线程负责执行,

2、如果来的请求大于核心线程数,会被加入到阻塞队列中等待。

3、如果阻塞队列满了,会扩容线程数,直到扩容到线程池最大容纳线程数,

4、如果还来请求就触发拒绝策略(默认用AbortPolicy抛异常)。

生活中的例子:

银行中星期天只有两个窗口开放,就相当于我们的核心线程(corepool)客户数量大于这两个线程的处理能力再来的客户就会被请到候客区,相当于我们的阻塞队列(BlokingQueue),如果等候区满了,就开始分配新的窗口直到线程池满,也就是(maximumPool),就开始触发拒绝策略,拒绝策略会阻止客户请求,相当于保安不让人进来。等到线程池中的空闲线程超过一段时间,就会自动自动销毁。
image.png

具体的流程图:
image.png

四种拒绝策略的介绍

什么时候会触发拒绝策略:

阻塞队列已满,并且线程池中的线程数达到了max。

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

CallerRunsPolicy:不会抛异常,也不会丢弃任务,而是把任务回退到调用者。

DiscardOldestPolicy:直接丢弃等待最久的任务。

DiscardPolicy:直接丢弃任务不做任何处理。

这四个类均实现了第七个参数(RejectedExecutionHandler)接口。

Executors创建线程池

Executors.newFixedThreadPool(); 创建一个固定大小的线程池

Executors.newSingleThreadExecutor(); 创建单一的线程池

Executors.newCachedThreadPool(); 创建一个带有缓冲的线程池可以自动扩容,用于负载较轻的服务器。



在开发中我们这三种线程池我们都不用。因为这三种的阻塞队列是LinkedBlockingQueue,虽然是有界阻塞队列,但是默认值确实int的最大值也就是2^31-1将近21亿。因此永远都不会满,就会出现任务一直在阻塞队列中得不到处理,会导致OOM(堆)异常。因此要手写线程池。

手写线程池

代码如下:

   public static void main(String[] args) {
        //JDKThreadPool();
        ExecutorService executorService = new ThreadPoolExecutor(
                2,
                5,
                2L,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        try{
            for(int i=1;i<=8;i++){ //模仿10次请求
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName() + "\t"+"办理业务");
                    }
                });
            }
        }finally{
            executorService.shutdown(); //用完归还给线程池
        }
    }

--最多能同时接受8个请求,超过8个就会触发拒绝策略。直接抛异常。

手写线程池最重要的就是ThreadPoolExecutor类里边传的7个参数。

如何配置合理线程池数

根据您的业务:

调用Runtime.getRuntime().availableProcessors()得到硬件cpu数。

cpu密集型:

最大线程数 = cpu数+1 (尽量减少上下文切换)




io密集型:(经常操作数据库)

io阻塞不高(操作数据库就会阻塞,慢)

最大线程数 = cpu数*2

io阻塞较高:

最大线程数 = cpu数/(1-阻塞系数) 阻塞系数通常0.8~0.9之间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值