13、线程池

程序的运行,本质就是消耗系统资源,当我们频繁的创建和销毁线程时,会十分浪费系统资源,因此为了优化资源的使用,我们就有必要使用线程池的技术。

池化技术:实现分配好一定的资源,当有需要用时,就从分配好的资源中取出,用完后再归还;

1、线程池的优势

(1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
(2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
(3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
总体来说,线程池的使用提高了线程的复用性,并且能对最大的并发数进行控制,并对线程进行管理;

2、线程池必会(重点)

3大方法,7大参数,4种拒绝策略

3大方法:

在阿里开发手册中,规定线程池不得使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式能让我们更加明确线程池的运行规则,规避资源耗尽的风险。

Executor创建线程池的风险如下:

  • 运行创建线程的最大数量为Integer的最大值,这个值约为21亿,这样会造成请求堆积,导致OOM。

由于线程的3大方法跟Executors有关,我们去学习这3大方法还是需要研究一下Executors;

Executors类似集合中的Collections一样,是一个工具类,在Executors中有3大方法:

  • Executors.newSingleThreadExecutor(); //创建单个线程
  • Executors.newFixedThreadPool(n); //创建指定线程
  • Executors.newCachedThreadPool(); //根据运行环境自动创建线程(遇强则强,遇弱则弱)

代码演示

public class ThreadPoolDemo_Executors {
    public static void main(String[] args) {

        ExecutorService executor = Executors.newSingleThreadExecutor(); //创建单个线程
        // ExecutorService executor = Executors.newFixedThreadPool(5);//创建指定线程
        // ExecutorService executor = Executors.newCachedThreadPool(); //根据运行环境自动创建线程(遇强则强,遇弱则弱)
        try {
            for (int i = 0; i < 10; i++) {
                //使用了线程池之后,我们不再用new Thread的方式创建线程,直接使用线程池来获取即可
                executor.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" is running");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //程序结束,一定要关闭线程池
            executor.shutdown();
        }
    }
}

运行结果
在这里插入图片描述

可以看到,这里打印了10次,都是同一条线程在运行。

现在切换为第二种创建线程池的方式,指定线程池的线程数:

Executors.newFixedThreadPool(n);

运行结果:
在这里插入图片描述

程序运行10次,最大使用到了5条线程同时运行

现在切换为第三种方式,弹性使用线程数:

Executors.newCachedThreadPool();

运行结果:
在这里插入图片描述

可以看到,最大支持了10个并发

7大参数

我们点进这3大方法,查看一下它们的源码:

//单一线程池
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService(
        new ThreadPoolExecutor(
            1, 
            1,
            0L, 
            TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>())
            );
    }
//指定线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(
        nThreads, 
        nThreads,
        0L,
        TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>()
        );
    }
//弹性线程池
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(
        0,
        Integer.MAX_VALUE,
        60L,
        TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>()
        );
    }

通过源码可以看出,这三个方法本质都是使用ThreadPoolExecutor来创建线程池的,我们再来找到ThreadPoolExecutor的源码:

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

可以看到在这个方法中,会传递进7个参数,这就是前面所说的7大参数

corePoolSize核心线程池大小
maximumPoolSize最大线程池大小
keepAliveTime超时不调用就会释放
unit超时时间单位
workQueue阻塞队列
threadFactory线程工厂,创建线程的,一般不动该参数
handler拒绝策略

假设一个场景:
在这里插入图片描述

  • 我们在银行办理业务,假设现在是有5个窗口,但是只开放了2个窗口,现在办理业务的人不多,2个窗口足以满足这些需要办理业务的人。(这2个窗口就可以看作核心线程池corePoolSize,是始终开放的)
  • 过了一段时间,办理业务的人逐渐增多,需要开始排队,而银行后候客区,办理业务的人就可以在候客区等候。(候客区就可以看作阻塞队列workQueue
  • 又过了一段时间,办理业务的人越来越多,2个窗口已经忙不过来了,并且候客区也满了,因此,银行就需要开放剩下没开放的3个窗口来帮助处理大量等待的客户办理业务。(这时,一共开放了5个窗口,而这5个窗口就是线程池的最大线程数maximumPoolSize
  • 到后面,越来越多的客户来办理业务,现在候客区也满了,所有窗口都在办理业务,银行处理不过来这么多客户要办理的业务,不得不要求后面进来的客户去别的地方办理,或者改天再来办理。(而这就是拒绝策略handler)。
  • 等到大部分客户办理完业务,3、4、5号窗口长时间没人办理了,就可以关闭3、4、5号窗口。(超时释放keepAliveTime。超时就一定会有一个时间单位unit)。

根据这个场景,我们完成这个案例的实现:

public class ThreadPoolDemo_ThreadPoolExecutor {
    public static void main(String[] args) {
        ExecutorService threadPool =  new ThreadPoolExecutor(
                2,          //银行已开放的两个窗口
                5,      //银行总窗口数
                1,         //超时时间长度
                TimeUnit.MICROSECONDS,      //时间单位
                new LinkedBlockingQueue<>(3),   //候客区大小
                Executors.defaultThreadFactory(),       //线程池
                new ThreadPoolExecutor.AbortPolicy());  //拒绝策略(默认策略)
        try {
            for (int i = 1; i <= 8 ; i++) {
                final int temp = i;
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"号窗口为第"+temp+"客户办理业务");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        	//一定要关闭线程池
            threadPool.shutdown();
        }
    }
}

运行结果:
在这里插入图片描述
可以看到,当有8个客户要办理业务时,所有窗口都会开辟,而最大的处理数量 = 线程池最大容量+阻塞队列长度。这时我们如果有9个人办理业务,程序就会抛出异常,实行拒绝策略。
当有9个人办理业务时,运行结果:
在这里插入图片描述
而抛出的异常的这个拒绝策略,就是我们在传递参数时的最后一个参数ThreadPoolExecutor.AbortPolicy()

4种拒绝策略

AbortPolicy不处理该线程,抛出异常
CallerRunPolicy哪里来的回哪里,返回给调用过来的线程处理,不会抛出异常
DiscardPolicy丢掉任务,不会抛出异常
DiscardOldestPolicy尝试与最早获取到资源的线程竞争,如果没有竞争到,会丢掉任务,不会抛出异常

扩展

在实际开发中,一般线程池的最大线程数会根据两种方式来确定:

  • CPU密集型:根据当前主机的核心线程数来确定最大线程;
    (在Java中可通过Runtime.getRuntime().availableProcessors()获取,在创建线程池时传递给maximumPoolSize即可)
  • IO密集型:判断程序中十分占用IO的线程,一般设置为该资源的2倍;
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值