暑期JAVA学习(36)线程池

线程池

    • 一、线程池概述
        • (1)什么是线程池?
        • (2)不使用线程池的问题
        • (3)线程池原理
    • 二、线程池实现的API、参数说明
        • (1)谁代表线程池?
        • (2)如何得到线程池对象
        • (3)ThreadPoolExecutor构造器的参数说明
        • (4)线程池常见面试题
          • ①临时线程什么时候创建?
          • ②什么时候会开始拒绝任务?
    • 三、线程池处理Runnable任务
        • (1)ThreadPoolExecutor创建线程池对象示例
        • (2)ExecutorService的常用方法
        • (3)新任务拒绝策略
        • (4)具体使用
            • ①当任务数少于等于核心线程数时不会创建新的线程
            • ②当任务数大于核心线程数并且小于等于核心线程数加上指定任务队列数目时,线程池认为现有的三个核心线程数忙的过来,也不会创建新的线程
            • ③如果任务数大于核心线程数加上指定任务队列数目时,临时线程就会开始创建了
            • ④ 此时核心线程和临时线程均在忙,任务队列也满了,就触发了拒绝策略,不再创建!!
        • (5)在使用ThreadPoolExecutor构造器时记不住参数有哪些怎么办呢?
        • (6)总结
    • 四、线程池处理Callable任务
        • (1)ExecutorService的常用方法
        • (2)线程池如何处理Runnable任务
        • (3)具体使用
    • 五、Executors工具类实现线程池
        • (1)Executors得到线程池对象的常用方法
        • (2)具体使用
        • (3)Executors使用可能存在的陷阱
        • (4)总结
          • ①Executors工具类底层是基于什么方式实现的线程池对象?
          • ②Executors是否适合做大型互联网场景的线程池方案?

一、线程池概述

(1)什么是线程池?

●线程池就是一个可以复用线程的技术。

(2)不使用线程池的问题

●如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。

(3)线程池原理

●首先会线程池会在内部放一些固定的工作线程,每当来一个任务时,就会生成一个核心线程来处理,再来一个任务,就会再生成一个核心线程来处理。暑期JAVA学习(36)线程池_第1张图片

暑期JAVA学习(36)线程池_第2张图片

●假设线程池控制只能生成三个工作线程,那么此时线程池就已经达到了上限,再进入新任务时就不会产生新的线程,而是已经生成的线程会等当前的任务完成之后,再去依次处理新的任务,从而实现三个线程可以处理很多的任务,避免突然生成很多的线程,严重影响系统的性能。

暑期JAVA学习(36)线程池_第3张图片
暑期JAVA学习(36)线程池_第4张图片
暑期JAVA学习(36)线程池_第5张图片
暑期JAVA学习(36)线程池_第6张图片
暑期JAVA学习(36)线程池_第7张图片

二、线程池实现的API、参数说明

(1)谁代表线程池?

●JDK 5.0起提供了代表线程池的接口:ExecutorService

(2)如何得到线程池对象

●方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象

●方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象

(3)ThreadPoolExecutor构造器的参数说明

暑期JAVA学习(36)线程池_第8张图片
●参数一:指定线程池的线程数量(核心线程): corePoolSize ----------------------------------> 不能小于0

●参数二:指定线程池可支持的最大线程数: maximumPoolSize -----------> 最大数量 >= 核心线程数量

●参数三:指定临时线程的最大存活时间: keepAliveTime -----------------------------------------> 不能小于0

●参数四:指定存活时间的单位(秒、分、时、天): unit ----------------------------------------------> 时间单位

●参数五:指定任务队列: workQueue ------------------------------------------------------------------> 不能为null

●参数六:指定用哪个线程工厂创建线程: threadFactory ------------------------------------------> 不能为null

●参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler ---------------------------> 不能为null

抽象化理解:
●把线程池假设为一个餐厅,任务当作顾客,线程当作员工

●参数一:指定线程池的线程数量(核心线程): corePoolSize,就相当于餐厅里的正式员工,始终存在。

●参数二:指定线程池可支持的最大线程数: maximumPoolSize ,就相当于餐厅里的最多员工数,包括了正式员工和临时员工。

●参数三:指定临时线程的最大存活时间: keepAliveTime,是当每个顾客都有一名正式员工服务时,临时员工就空闲下来了,临时线程的最大存活时间就相当于临时员工最多可以空闲的多久后被开除。

●参数四:指定存活时间的单位(秒、分、时、天): unit ,相当于指定临时员工可以空闲的多久后被开除。

●参数五:指定任务队列: workQueue,就相当于餐厅外给等待的顾客坐的座位,座位数目是固定的。

●参数六:指定用哪个线程工厂创建线程: threadFactory,就相当于是餐厅里招服务员的HR

●参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler,假设餐厅里有10个正式员工,5个临时员工,来了15个顾客,此时每个员工都在忙,并且餐厅外等待的座位也坐满了,此时如果再来客人如何处理,就是handler

(4)线程池常见面试题
①临时线程什么时候创建?

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

②什么时候会开始拒绝任务?

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

三、线程池处理Runnable任务

(1)ThreadPoolExecutor创建线程池对象示例

在这里插入图片描述

(2)ExecutorService的常用方法
方法名称 说明
void execute(Runnable command) 执行任务/命令,没有返回值,一般用来执行 Runnable 任务
Future submit(Callable task) 执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务
void shutdown() 等任务执行完毕后关闭线程池
List shutdownNow() 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务
(3)新任务拒绝策略
策略 详解
ThreadPoolExecutor.AbortPolicy 丢弃任务并抛出RejectedExecutionException异常。是默认的策略
ThreadPoolExecutor.DiscardPolicy 丢弃任务,但是不抛出异常 这是不推荐的做法
ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务 然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy 由主线程负责调用任务的run()方法从而绕过线程池直接执行
(4)具体使用

暑期JAVA学习(36)线程池_第9张图片

①当任务数少于等于核心线程数时不会创建新的线程

暑期JAVA学习(36)线程池_第10张图片
暑期JAVA学习(36)线程池_第11张图片
暑期JAVA学习(36)线程池_第12张图片

②当任务数大于核心线程数并且小于等于核心线程数加上指定任务队列数目时,线程池认为现有的三个核心线程数忙的过来,也不会创建新的线程

暑期JAVA学习(36)线程池_第13张图片
暑期JAVA学习(36)线程池_第14张图片
暑期JAVA学习(36)线程池_第15张图片

③如果任务数大于核心线程数加上指定任务队列数目时,临时线程就会开始创建了

暑期JAVA学习(36)线程池_第16张图片
暑期JAVA学习(36)线程池_第17张图片
暑期JAVA学习(36)线程池_第18张图片

④ 此时核心线程和临时线程均在忙,任务队列也满了,就触发了拒绝策略,不再创建!!

暑期JAVA学习(36)线程池_第19张图片
暑期JAVA学习(36)线程池_第20张图片
暑期JAVA学习(36)线程池_第21张图片
拒绝策略出发后,会丢弃任务并抛出RejectedExecutionException异常:

Exception in thread “main” java.util.concurrent.RejectedExecutionException: Task SummerDay36_threadpool.MyRunnable@34a245ab rejected from java.util.concurrent.ThreadPoolExecutor@7cc355be[Running, pool size = 5, active threads = 5, queued tasks = 5, completed tasks = 0]

(5)在使用ThreadPoolExecutor构造器时记不住参数有哪些怎么办呢?

暑期JAVA学习(36)线程池_第22张图片
不慌,根本不用强行记忆,按住Ctrl键,光标移到ThreadPoolExecutor上,单击后就会出现选择声明,选择最后一个有七个参数的声明点击,就可以看见这七个参数的具体使用啦~
暑期JAVA学习(36)线程池_第23张图片
暑期JAVA学习(36)线程池_第24张图片

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + "输出了:" + i);
        }
        try {
            System.out.println(Thread.currentThread().getName() + "本任务与线程绑定了,线程在忙");
            Thread.sleep(1000000);//让线程睡眠
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/**
 * 目标:自定义一个线程池,并测试其特性
 */
public class ThreadDemo01 {
    public static void main(String[] args) {
        //1.创建线程池对象
        /**
         * public ThreadPoolExecutor(int corePoolSize,
         *                               int maximumPoolSize,
         *                               long keepAliveTime,
         *                               TimeUnit unit,
         *                               BlockingQueue workQueue,
         *                               ThreadFactory threadFactory,
         *                               RejectedExecutionHandler handler)
         */
        ExecutorService pool = new ThreadPoolExecutor(3,5,6,
                TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        //2.给任务线程池处理
        Runnable target = new MyRunnable();

        //① corePoolSize = 3 最大核心线程数是三,当任务数少于等于三个时不会创建新的线程
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);

        //② BlockingQueue workQueue = new ArrayBlockingQueue<>(5)
        //当任务数 > 3 并且 <= (5+3) 时,线程池认为现有的三个核心线程数忙的过来,也不会创建新的线程
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);

        //③ 如果任务数 > 8 时,临时线程就会开始创建了
        pool.execute(target);
        pool.execute(target);

        //④ 此时三个核心线程和两个临时线程均在忙,任务满了,就触发了拒绝策略,不再创建!!
        pool.execute(target);
        pool.execute(target);

        //⑤ 关闭线程池(开发中一般是不会使用的)
        //pool.shutdownNow();//立即关闭,即使任务没有完成,丢失任务的!
        pool.shutdown();//会等待全部任务执行完毕之后再关闭

    }
}
(6)总结

线程池如何处理Runnable任务?
●使用ExecutorService的方法:
●void execute(Runnable target)

四、线程池处理Callable任务

(1)ExecutorService的常用方法
方法名称 说明
void execute(Runnable command) 执行任务/命令,没有返回值,一般用来执行 Runnable 任务
Future submit(Callable task) 执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务
void shutdown() 等任务执行完毕后关闭线程池
List shutdownNow() 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务
(2)线程池如何处理Runnable任务

●使用ExecutorService的方法:
●void execute(Runnable target)

(3)具体使用

暑期JAVA学习(36)线程池_第25张图片
暑期JAVA学习(36)线程池_第26张图片
在这里插入图片描述

/**
 1、定义一个任务类 实现Callable接口  应该申明线程任务执行完毕后的结果的数据类型
 */
public class MyCallable implements Callable {
    private int n;
    public MyCallable(int n) {
        this.n = n;
    }

    /**
     2、重写call方法(任务方法)
     */
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= n ; i++) {
            sum += i;
        }
        return Thread.currentThread().getName()
                + "执行 1-" + n + "的和,结果是:" + sum;
    }
}
public class ThreadDemo02 {
    public static void main(String[] args) throws Exception {
        // 1、创建线程池对象
        /**
         public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
         long keepAliveTime,
         TimeUnit unit,
         BlockingQueue workQueue,
         ThreadFactory threadFactory,
         RejectedExecutionHandler handler)
         */
        ExecutorService pool = new ThreadPoolExecutor(3, 5 ,
                6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5) , Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy() );

        //2.给任务线程池处理
        Future f1 = pool.submit(new MyCallable(100));
        Future f2 = pool.submit(new MyCallable(200));
        Future f3 = pool.submit(new MyCallable(300));
        Future f4 = pool.submit(new MyCallable(400));

//        String rs = f1.get();
//        System.out.println(rs);

        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());
    }
}

五、Executors工具类实现线程池

(1)Executors得到线程池对象的常用方法

●Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。

方法名称 说明
public static ExecutorService newCachedThreadPool() 线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间则会被回收掉。
public static ExecutorService newFixedThreadPool​(int nThreads) 创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。
public static ExecutorService newSingleThreadExecutor () 创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。
public static ScheduledExecutorService newScheduledThreadPool​(int corePoolSize) 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。

●注意:Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的。

(2)具体使用

暑期JAVA学习(36)线程池_第27张图片
暑期JAVA学习(36)线程池_第28张图片
暑期JAVA学习(36)线程池_第29张图片

(3)Executors使用可能存在的陷阱

●大型并发系统环境中使用Executors如果不注意可能会出现系统风险。

方法名称 说明
public static ExecutorService newFixedThreadPool​(int nThreads) 允许请求的任务队列长度是Integer.MAX_VALUE,可能出现OOM错误( java.lang.OutOfMemoryError )
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newCachedThreadPool() 创建的线程数量最大上限是Integer.MAX_VALUE,线程数可能会随着任务1:1增长,也可能出现OOM错误(java.lang.OutOfMemoryError)
public static ScheduledExecutorService newScheduledThreadPool​(int corePoolSize)
(4)总结
①Executors工具类底层是基于什么方式实现的线程池对象?

●线程池ExecutorService的实现类:ThreadPoolExecutor

②Executors是否适合做大型互联网场景的线程池方案?

●不合适。
●建议使用ThreadPoolExecutor来指定线程池参数,这样可以明确线程池的运行规则,规避资源耗尽的风险。

你可能感兴趣的:(java,学习,jvm)