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

线程池[重点]

一、概述

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

1、问题?

不使用线程池的问题

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

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

在这里插入图片描述
在这里插入图片描述



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

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

2、如何得到线程池对象?
方式一[重点]:
  • 使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象

    在这里插入图片描述


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

在这里插入图片描述



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

    在这里插入图片描述



  • 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<Runnable> 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());
    }
}

在这里插入图片描述



总结

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

  • 使用ExecutorService的方法
    • Future< T > submit(Callable< T > command)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值