101-Java的线程池[重点]:概述、7个参数详解、处理Runnable、Callable任务、Executors的工具类构建线程池对象

线程池[重点]

一、概述

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

1、问题?

不使用线程池的问题

  • 如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的(占用内存空间、CPU资源),这样会严重影响系统的性能。

2、工作原理

  • 线程池中有三个人,这三个人就相当于三个线程、三个员工;
  • 三个人下面的多个红点,就是每个用户发起的请求;
  • 这三个人(线程),一人处理一个请求,处理完一个再接着处理下一个;
  • 这样就避免了不断要创建新线程来处理每个用户发起的请求,将内存、CPU资源耗尽的风险!!

101-Java的线程池[重点]:概述、7个参数详解、处理Runnable、Callable任务、Executors的工具类构建线程池对象_第1张图片
101-Java的线程池[重点]:概述、7个参数详解、处理Runnable、Callable任务、Executors的工具类构建线程池对象_第2张图片



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

1、谁代表线程池?

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

2、如何得到线程池对象?

方式一[重点]:
  • 使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象

    101-Java的线程池[重点]:概述、7个参数详解、处理Runnable、Callable任务、Executors的工具类构建线程池对象_第3张图片


(1)ThreadPoolExecutor构造器的参数说明
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
参数 说明 备注
corePoolSize 指定线程池的线程数量(核心线程) 不能小于0
maximumPoolSize 指定线程池可支持的最大线程数 最大数量 >= 核心线程数量
keepAliveTime 指定临时线程的最大存活时间 不能小于0
unit 指定存活时间的单位(秒、分、时、天) 时间单位
workQueue 指定任务队列 不能为null
threadFactory 指定用哪个线程工厂创建线程 不能为null
handler 指定线程忙、任务满的时候,新任务来了如何做 不能为null

(2)结合实际生活说明7个参数
  • KTV:
    • 如果你不用线程池的话:
      • 每来一个客人,你就得立马招一个新服务员来招待客人;
      • 如果一次性来几百个客人,这样就得招几百个新服务员,这样成本就高了,资源就浪费了,亏本!!
    • 如果你使用线程池的话:
      • 那先招3个正式员工(corePoolSize),正式员工不可开除,最多支持10个员工(maximumPoolSize);
      • 这样就可以招7个临时员工,最多可以在KTV工作2天(keepAliveTime),2天后还没有新任务,那就开除掉这7个临时员工;
      • 在KTV门口放置5个座位(workQueue),给客人排队用的;
      • KTV人力资源(threadFactory)负责招人,最多可招7人,因为有3个是正式员工;
      • 3个正式员工(corePoolSize)、7个临时员工(maximumPoolSize)都在忙的时候,KTV门口的5个座位(workQueue)也满了,此时新来了一个客人,该怎么应对(handler)?


(3)线程池常见面试题

临时线程什么时候创建?

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

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

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



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

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


package com.app.d8_thread_pool;

/**
    定义线程任务类
 */
public class MyRunnable extends Thread{
    /**
        重写run方法(里面是线程任务)
     */
    @Override
    public void run() {
        // 获取线程名称
        String name = Thread.currentThread().getName();
        for (int i = 1; i <= 3; i++) {
            System.out.println(name + "线程输出了第" + i + "个HelloWorld!!");
        }

        // 模拟线程很忙的时候
        try {
            System.out.println(name + "线程与任务绑定了,线程进入休眠了~~");
            Thread.sleep(100000000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
package com.app.d8_thread_pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
    目标:使用Executors工具类实现线程池。
    它的底层也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的
 */
public class ThreadPoolDemo3 {
    public static void main(String[] args) {
        // 1、创建固定线程数量的线程池
        ExecutorService pools = Executors.newFixedThreadPool(3);

        // 2、创建线程任务交给线程池处理
        pools.execute(new MyRunnable());
        pools.execute(new MyRunnable());
        pools.execute(new MyRunnable());

        // 已超出固定线程池的数量范围,因此该任务不会执行!!
        pools.execute(new MyRunnable());
    }
}

101-Java的线程池[重点]:概述、7个参数详解、处理Runnable、Callable任务、Executors的工具类构建线程池对象_第4张图片



(2)使用Executors的注意事项
  • 大型并发系统环境中使用Executors如果不注意可能会出现系统风险。

    101-Java的线程池[重点]:概述、7个参数详解、处理Runnable、Callable任务、Executors的工具类构建线程池对象_第5张图片



  • newFixedThreadPool(int nThreads)、newSingleThreadExecutor():
    • 底层没有对任务队列的数量做限制,因此可以无限添加线程任务,可能会导致内存溢出!
  • newCachedThreadPool()、newScheduledThreadPool(int corePoolSize):
    • 底层没有对线程、任务队列做数量限制,因此可能会导致内存、CPU资源耗尽!
方法名称 说明
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
允许请求的任务队列长度是
Integer.MAX_VALUE,
可能出现OOM错误(java.lang.OutOfMemoryError)
public static ExecutorService newCachedThreadPool()
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建的线程数量最大上限是
Integer.MAX_VALUE,
线程数可能会随着任务1:1增长,
也可能出现OOM错误(java.lang.OutOfMemoryError)


总结

1、Executors工具类底层是基于什么方式实现线程池对象的?

  • 线程池ExecutorService的实现类:ThreadPoolExecutor

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

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


三、线程池处理Runnable任务

1、ThreadPoolExecutor创建线程池对象

ExecutorService pools = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS, new ArrayBlockingQueue<>(6), 
                                              Executors.defaultThreadFactory(), 
                                              new ThreadPoolExecutor.AbortPolicy());


2、ExecutorService的常用API

方法名称 说明
void execute(Runnable command) 执行任务/命令,无返回值,一般用来执行 Runnable 任务
Future< T > submit(Callable< T > task) 执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务
void shutdown() 等待全部任务执行完毕后关闭线程池
List< Runnable > shutdownNow() 立刻关闭,停止正在执行的所有任务,并返回队列中未执行的任务


3、新任务拒绝策略

策略 说明
ThreadPoolExecutor.AbortPolicy 丢弃任务并抛出 RejectedExecutionException 异常(是默认的策略
ThreadPoolExecutor.DiscardPolicy 丢弃任务,但是不抛出异常(不推荐
ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务,然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy 由主线程负责调用任务的 run() 方法从而绕过线程池直接执行(相当于是老板亲自服务


4、范例

package com.app.d8_thread_pool;

/**
    定义线程任务类
 */
public class MyRunnable extends Thread{
    /**
        重写run方法(里面是线程任务)
     */
    @Override
    public void run() {
        // 获取线程名称
        String name = Thread.currentThread().getName();
        for (int i = 1; i <= 3; i++) {
            System.out.println(name + "线程输出了第" + i + "个HelloWorld!!");
        }

        // 模拟线程很忙的时候
        try {
            System.out.println(name + "线程与任务绑定了,线程进入休眠了~~");
            Thread.sleep(100000000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
package com.app.d8_thread_pool;

import java.util.concurrent.*;

/**
    目标:学习使用线程池处理 Runnable 任务
    作用:线程池大大提高了线程的复用技术,不需要为每个新任务都创建一个新线程!!
 */
public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        // 1、创建线程池
        /*
            public ThreadPoolExecutor(int corePoolSize,
                                      int maximumPoolSize,
                                      long keepAliveTime,
                                      TimeUnit unit,
                                      BlockingQueue workQueue,
                                      ThreadFactory threadFactory,
                                      RejectedExecutionHandler handler)
         */
        ExecutorService pools = new ThreadPoolExecutor(3, 5, 5, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(6), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());


        // 2、创建 Runnable 任务
        Runnable target = new MyRunnable();


        /**
            3、将Runnable任务交给线程池处理
         */
        // a.3个核心线程被占用
        pools.execute(target);
        pools.execute(target);
        pools.execute(target);

        // b.任务队列已满6个
        pools.execute(target);
        pools.execute(target);
        pools.execute(target);
        pools.execute(target);
        pools.execute(target);
        pools.execute(target);

        // c.核心线程在忙,任务队列已满,开始创建临时线程
        pools.execute(target);
        pools.execute(target);

        // d.3个核心线程 + 2个临时线程,已达到线程池最大线程数量,此时开始触发新任务拒绝策略
        try {
            pools.execute(target);  // 抛出异常: RejectedExecutionException
        } catch (Exception e){  // 捕获异常!
            System.out.println("临时线程数量已超出线程池最大线程数量~~");
            e.printStackTrace();
        }

        // 4、关闭线程池(开发中一般不会使用:因为无论是系统、网站、购物网等等,都是要一直处理每个用户发起的请求的。)
        // pools.shutdownNow();    // 立即关闭线程池,即使任务没有完成,也会关闭,会丢失任务!!
        // pools.shutdown();       // 等待所有任务执行完毕后再关闭线程池!!
    }
}
临时线程数量已超出线程池最大线程数量~~
pool-1-thread-2线程输出了第1个HelloWorld!!
pool-1-thread-5线程输出了第1个HelloWorld!!
pool-1-thread-3线程输出了第1个HelloWorld!!
pool-1-thread-1线程输出了第1个HelloWorld!!
pool-1-thread-3线程输出了第2个HelloWorld!!
pool-1-thread-5线程输出了第2个HelloWorld!!
pool-1-thread-4线程输出了第1个HelloWorld!!
pool-1-thread-2线程输出了第2个HelloWorld!!
pool-1-thread-4线程输出了第2个HelloWorld!!
pool-1-thread-5线程输出了第3个HelloWorld!!
pool-1-thread-3线程输出了第3个HelloWorld!!
pool-1-thread-1线程输出了第2个HelloWorld!!
pool-1-thread-4线程输出了第3个HelloWorld!!
pool-1-thread-2线程输出了第3个HelloWorld!!
pool-1-thread-1线程输出了第3个HelloWorld!!
pool-1-thread-4线程与任务绑定了,线程进入休眠了~~
pool-1-thread-5线程与任务绑定了,线程进入休眠了~~
pool-1-thread-1线程与任务绑定了,线程进入休眠了~~
pool-1-thread-2线程与任务绑定了,线程进入休眠了~~
pool-1-thread-3线程与任务绑定了,线程进入休眠了~~
java.util.concurrent.RejectedExecutionException: Task Thread[Thread-0,5,main] rejected from java.util.concurrent.ThreadPoolExecutor@378bf509[Running, pool size = 5, active threads = 5, queued tasks = 6, completed tasks = 0]
	at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2065)
	at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:833)
	at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1365)
	at com.app.d8_thread_pool.ThreadPoolDemo1.main(ThreadPoolDemo1.java:51)
	


总结

1、线程池如何处理Runnable任务?

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


四、线程池处理Callable任务

1、范例

package com.app.d8_thread_pool;

import java.util.concurrent.Callable;

/**
    定义线程任务类
 */
public class MyCallable implements Callable<String> {
    private int n;  // 定义变量,用于存储一个数
    public MyCallable(int n) {
        this.n = n;
    }

    @Override
    public String call() throws Exception {
        int sum = 0;    // 定义变量,用于求和
        // 计算1-n的和
        for (int i = 1; i <= n; i++) {
            sum += i;
        }
        // 返回线程执行任务的结果
        return Thread.currentThread().getName() + "线程执行计算 1-" + n + " 的和,结果是:" + sum;
    }
}
package com.app.d8_thread_pool;

import java.util.concurrent.*;

/**
    目标:使用线程池处理 Callable 任务
    作用:线程池大大提高了线程的复用技术,不需要为每个新任务都创建一个新线程!!
 */
public class ThreadPoolDemo2 {
    public static void main(String[] args) throws Exception {
        // 1、创建线程池
        ExecutorService pools = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5), new ThreadPoolExecutor.AbortPolicy());

        // 2、创建Callable线程任务并交给线程池处理
        Future<String> s1 = pools.submit(new MyCallable(100));
        Future<String> s2 = pools.submit(new MyCallable(200));
        Future<String> s3 = pools.submit(new MyCallable(300));
        Future<String> s4 = pools.submit(new MyCallable(400));
        Future<String> s5 = pools.submit(new MyCallable(500));
        Future<String> s6 = pools.submit(new MyCallable(600));

        // 3、获取线程执行任务完毕后的结果并输出
//        String rs = s1.get();
//        System.out.println(rs);
        System.out.println(s1.get());
        System.out.println(s2.get());
        System.out.println(s3.get());
        System.out.println(s4.get());
        System.out.println(s5.get());
        System.out.println(s6.get());
    }
}

101-Java的线程池[重点]:概述、7个参数详解、处理Runnable、Callable任务、Executors的工具类构建线程池对象_第6张图片



总结

1、线程池如何处理Callable任务,并得到任务执行完成后返回的结果?

  • 使用ExecutorService的方法
    • Future< T > submit(Callable< T > command)

你可能感兴趣的:(JavaSE基础进阶篇,java,jvm,开发语言)