Java线程池

1、线程池的概念

​ 在使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间,线程也属于宝贵的系统资源。

​ **线程池:**其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。由于线程池中有很多操作都是与优化资源相关的,这里就不多赘述。

合理利用线程池能够带来三个好处:

  1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

2、线程池的使用

获取线程池的方式有两种:

  • 利用工具类Executors里面的静态方法
  • 自己new对象

2.1 创建池子(自动创建线程池不推荐)

Executors类中有个创建线程池的方法如下:

  • public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。

    (创建的是有上限的线程池,也就是池中的线程个数可以指定最大数量)

  • public static ExecutorService newCachedThreadPool():返回线程池对象。

    (很多资料说没有上限,其实不对,上限很大,为int的最大值 下面会分析)

2.2 提交任务

  • public Future<?> submit(Runnable task):将任务提交给线程池

    Future接口:用来记录线程任务执行完毕后产生的结果。

    如果是用多线程的第三种方式提交任务,那么可以获取线程运行的结果。

2.3 提交后的情况

  • 有空闲线程就执行

  • 没有空闲线程,但是线程池中的核心线程还没有到达上限,则创建新的线程执行提交的任务

  • 没有空闲线程,但是线程池中的核心线程已经到达上限,则排队等待

  • 排队的线程有没有数量限制呢?如果排队的任务数量到上限了怎么办?

    此时就会创建临时线程。

  • 当核心线程都在忙,临时线程也在忙,而且队伍也排满了,那么就会触发任务的拒绝策略。

2.4 使用线程池中线程的步骤

  1. 创建线程池对象。
  2. 将要执行的任务交给池子
  3. 关闭线程池(一般不做)。

Runnable实现类代码:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("我要一个教练");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("教练来了: " + Thread.currentThread().getName());
        System.out.println("教我游泳,交完后,教练回到了游泳池");
    }
}

线程池测试类:

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 创建线程池对象
        ExecutorService pool = Executors.newFixedThreadPool(2);//包含2个线程对象

        // 将要执行的任务提交给线程池
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        // 注意:submit方法调用结束后,将使用完的线程又归还到了线程池中
        // 销毁线程池,相当于把池子给砸了,在开发中一般不砸
        // service.shutdown();
    }
}

2.5 源码分析

2.5.1 Executors.newFixedThreadPool 方法分析


Test:

1号池中的线程1,2,3执行完后,经过3秒等待,执行完后,归还线程到线程池中,3秒过后,又从线程池中获取线程,又执行了,thread -1,thread -2,thread -3。

2.5.2 Executors.newFixedThreadPool 方法分析

Executors.newFixedThreadPool 中的参数为:4,这里的4只是最大线程容量为4,很多资料里面写着是初始容量为4,下面是最大容量的验证。

步骤:利用Debug 去分析

发现 pool size 这个属性初始为0,接下来继续执行。

pool size 为 1,使用的线程数为1。

pool size 为 2,使用的线程数为2。

pool size 为 3,使用的线程数为3。

pool size 为 4,使用的线程数为4。

发现在执行第五次的时候, pool size还是为 4,然后继续执行,发现size都是4, 那么下面提交的线程去哪了?其实都进行了队列,下面会分析。

2.5.3 小结

Executors.newFixedThreadPool 与 Executors.newFixedThreadPool 方法底层都是回返了ThreadPoolExecutor 线程池对象。

通过两个方法的源码进去看发现是一个ThreadPoolExecutor的带参构造方法,然后通过API帮助文档,看到ThreadPoolExecutor类有多个不同参数的构造方法。

那么这么多的参数,分别代表什么意思呢?

3、自己创建线程池的对象(推荐使用)

步骤:直接创建ThreadPoolExecutor的对象即可。

参数:

  • 参数一:核心线程数量(不能小于0)
  • 参数二:最大线程数(不能小于等于0,最大数量 >= 核心线程数量)
  • 参数三:空闲线程最大存活时间(不能小于0)
  • 参数四:时间单位(时间单位)
  • 参数五:任务队列也叫阻塞队列(不能为null)
  • 参数六:创建线程工厂,创建线程的方式(不能为null)
  • 参数七:任务的拒绝策略,要执行任务过多时的解决方案(不能为null)

分析(用餐厅举例子):

​ 参数一可以理解为:正式员工数量

​ 参数二可以理解为:餐厅最大员工数

​ 参数三可以理解为:临时员工空闲多长时间被辞退(空闲时间值)

​ 参数四可以理解为:临时员工空闲多长时间被辞退(空闲时间单位,秒、分、时)

​ 参数五可以理解为:排队客户

​ 参数六可以理解为:从哪来招人

​ 参数七可以理解为:当排队人数过多,超出顾客请下次再来(拒绝策略)

3.1 队列的种类

队列分为两种,有界和无界

  • ArrayBlockingQueue:有界队列,我们在创建对象的时候可以指定上限,也必须指定上限
  • LinkedBlockingQueue:无界队列,但是事实上还是有界的,只不过队伍可以很长,int的最大值21亿多

3.2 任务的拒绝策略

分为四种:

  • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常,是默认的策略
  • ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常 这是不推荐的做法
  • ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列中等待最久的任务 然后把当前任务加入队列中
  • ThreadPoolExecutor.CallerRunsPolicy:调用任务的run()方法绕过线程池直接执行。

3.3 临时线程创建时机

新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

注意:核心线程在忙,再有任务提交,先排队,队伍满了,再开临时线程。

3.4 触发拒绝策略时机

核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝

简单理解:所有的都满了,才会拒绝。

3.5 案例分析

MyThread类

package cn.itxiaoli.pool;

/**
 * @author xiaoli
 * @className MyThread
 * @description:
 * @date 2022/3/18 16:19
 */
public class MyThread implements Runnable{


    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"执行了");
        while (true){

        }
    }
}

Test类

package cn.itxiaoli.pool;

import java.util.concurrent.*;

/**
 * @author xiaoli
 * @className Test
 * @description:
 * @date 2022/3/18 16:19
 */
public class Test {
    public static void main(String[] args) {

         ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                // 核心线程数量
                3, 
                // 最大线程的数量
                5, 
                // 空闲时间值
                60, 
                // 空闲时间单位
                TimeUnit.SECONDS, 
                // 指定任务队列,最多为3
                new ArrayBlockingQueue<>(3), 
                // 创建线程的方式,底层就是进行了new Thread
                Executors.defaultThreadFactory(), 
                // 默认的拒绝策略,当队列满了而且线程池中所有的线程都在运行时,再添加任务则会触发
                new ThreadPoolExecutor.AbortPolicy() 
        );

        threadPoolExecutor.submit(new MyThread());
        threadPoolExecutor.submit(new MyThread());
        threadPoolExecutor.submit(new MyThread());
        threadPoolExecutor.submit(new MyThread());
        threadPoolExecutor.submit(new MyThread());
        threadPoolExecutor.submit(new MyThread());
        threadPoolExecutor.submit(new MyThread());
        threadPoolExecutor.submit(new MyThread());

    }
}

3.5.1 Debug

第一次执行时:

pool size = 1 , workQueue=0 ,workQueue为队列

第二次执行时:

pool size = 2 , workQueue=0

第三次执行时:

pool size = 3 , workQueue = 0

第四次执行时:

pool size = 3 , workQueue = 1,队列执行。

第五次执行时:

pool size = 3 , workQueue = 2

第六次执行时:

pool size = 3 , workQueue = 3

第七次执行时:

pool size = 4 , workQueue = 3 ,发现没有运行队列中的地址值没变,也就是说没有运行队列中的任务,而是直接创建了临时线程。

第八次执行时:

pool size = 5 , workQueue = 3 ,池中数量为5

第九次执行时:

程序曝出异常,执行拒绝策略

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值