线程池的创建和使用

发布时间:2024年01月04日

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

线程池复习梳理

线程池概念

线程池其实就是一个容纳多个线程的容器,其中线程可以反复利用,省去了频繁创建线程对象的操作,无序
在这里插入图片描述
合理利用线程池能够带来三个好处:

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

线程池的创建

创建线程的两种方式

  • 通过ExecutorService的实现类ThreadPoolExecutor,通过创建ThreadPoolExecutor对象来实现线程池的创建。
    通过阅读源码:
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

可以看到这个类的构造器,包含了一些参数。
稍后解释这些参数的含义
corePoolSize:指定线程池的核心线程数量。
maximumPoolSize:指定线程池最大线程数量。
keepAliveTime:指定临时线程的存活时间。(临时线程闲置了**时间后就会被干掉)
unit:指定临时线程的存活时间单位。
workQueue:指定线程池的任务队列。
threadFactory:指定线程池的线程工厂。(指定哪个线程工厂来创建)
handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新来的任务该如何处理)。
在这里插入图片描述

  • 通过Executors(线程池工具类)调用一些方法,来创建线程池

例子

 public static void main(String[] args) {
       /* public ThreadPoolExecutor(int corePoolSize,
        int maximumPoolSize,
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue,
        RejectedExecutionHandler handler) {*/
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
    }

线程池的注意事项:

  1. 临时线程什么时候创建?
    答:新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以允许创建临时线程,此时才会去创建临时线程。
  2. 什么时候开始拒绝新任务?
    核心线程、临时线程都子啊忙,任务队列也满了,此时才会去拒绝。

线程池处理Runnable任务

在这里插入图片描述
下面用一个例子来说明:
先创建一个Runnable任务

public class MineRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"==> 输出666");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

在主线程中创建线程池,并且分配执行

public class ThreadPoolsDemo01 {
    public static void main(String[] args) {
       /* public ThreadPoolExecutor(int corePoolSize,
        int maximumPoolSize,
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue,
        RejectedExecutionHandler handler) {*/
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        MineRunnable target = new MineRunnable();
        pool.execute(target); //线程池会自动创建一个新线程,自动处理这个任务,自动执行!
        pool.execute(target); //线程池会自动创建一个新线程,自动处理这个任务,自动执行!
        pool.execute(target); //线程池会自动创建一个新线程,自动处理这个任务,自动执行!
        pool.execute(target); //复用核心线程,执行任务
        pool.execute(target); //复用核心线程,执行任务
    }
}

我们增加几个线程去填满等待队列试试看:

pool.execute(target); //线程池会自动创建一个新线程,自动处理这个任务,自动执行!
        pool.execute(target); //线程池会自动创建一个新线程,自动处理这个任务,自动执行!
        pool.execute(target); //线程池会自动创建一个新线程,自动处理这个任务,自动执行!
        pool.execute(target); //复用核心线程,执行任务
        pool.execute(target); //复用核心线程,执行任务
        pool.execute(target); //复用核心线程,执行任务
        pool.execute(target); //复用核心线程,执行任务
        pool.execute(target); //复用核心线程,执行任务

在这里插入图片描述
可以看到多了几个临时线程。

那我们再次添加呢?
在这里插入图片描述
可以看到这里抛出了异常,这就是任务拒绝策略,当前使用的拒绝策略是直接丢弃任务,并且抛出异常。

任务拒绝策略

在这里插入图片描述
以上代码都是使用AbortPolicy的结果
当我们改成CallerRunsPolicy呢?
在这里插入图片描述
可以看到,主线程也开始输出了

线程池处理Callable任务

  • Callable和Runnable任务有什么不同?
    Callable任务是可以返回任务结果的
    在这里插入图片描述
    一样做一个例子演示:
    创建Callable的任务线程类
public class MineCallable implements Callable {
    private int n;

    public MineCallable(int n) {
        this.n = n;
    }

    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int  i = 0; i<n;i++){
            sum += i;
        }
        return  Thread.currentThread().getName()+ "==>" + n + "数求和的结果是:" + sum;
    }
}


public class MineCallable implements Callable {
    private int n;

    public MineCallable(int n) {
        this.n = n;
    }

    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int  i = 0; i<n;i++){
            sum += i;
        }
        return  Thread.currentThread().getName()+ "==>" + n + "数求和的结果是:" + sum;
    }
}

在这里插入图片描述

Executor工具类实现线程池(少使用)

在这里插入图片描述

 //通过Executors创建一个线程对象
        ExecutorService pool1 = Executors.newFixedThreadPool(3);

Executors使用可能存在的陷阱

  • 大型并发系统环境中使用Executors如果不注意会出现系统风险。
    在这里插入图片描述

OOM:内存溢出异常
Executors创建的线程,会因为任务过多/创建线程过多,导致内存溢出。
前两者会导致任务过多堆积
最后的Cached,多少任务进入多少线程就会被创建

注意:
这些方法的底层,都是通过线程池实现类ThreadExecutor创建的线程池对象。

核心线程数量到底配置多少呢?

  • 计算密集型任务:核心线程数量 = CPU核数 + 1;
  • IO密集型任务:核心线程数 = CPU核数*2 + 1;
    计算密集型:大多由CPU去计算的任务,比如100的累加(计算)
    IO密集型:提取文件数据,通信。
    CPU核数: Ctrl + Alt + Delete键
    打开任务管理器 --> 性能
    在这里插入图片描述
文章来源:https://blog.csdn.net/weixin_70496041/article/details/135360507
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。