Edehuag

导航

线程池

线程池概述

线程池是一个可以去复用线程的技术。
不使用线程池的问题:
用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理的,创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能。

创建线程池

JDK5.0起提供了代表线程池的接口:ExecutorService。

创建线程池的方式:
方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。
ExecutorService=》ThreadPoolExecutor
方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。

通过ThreadPoolExecutor创建线程池

ThreadPoolExecutor类提供的构造器 作用
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueueThreadFactory threadFactory,RejectedExecutionHandler handler) 使用初始化参数创建一个线程池对象
参数说明:
参数一:corePoolSize:指定线程池的核心线程的数量。
参数二:maximumPoolSize:指定线程池的最大线程数量。
参数三:keepAliveTime:指定临时线程的存活时间。
参数四:unit:指定临时线程存活的时间单位(秒、分、时、天)
参数五:workQueue:指定线程池的任务队列。
参数六:threadFactory:指定线程池的线程工厂。
参数七:handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理)

ExecutorService的常用方法:

方法名称 说明
void execute(Runnable command) 执行 Runnable 任务
Future submit(Callabletask) 执行Callable 任务,返回未来任务对象,用于获取线程返回的结果
void shutdown() 等全部任务执行完毕后,再关闭线程池!
List shutdownNow() 立刻关闭线程池,停止正在执行的任务,并返回队列中未执行的任务

线程池处理Runnable任务代码

package com.example.demo.MyExecutor;

import java.util.concurrent.*;

public class ExecutorServiceDemo1 {
    public static void main(String[] args) {
        //使用线程池的实现类ThreadPoolExecutor创建线程池
        //new ArrayBlockingQueue<>(5)代表任务队列可以排队3个
        // new ThreadPoolExecutor.AbortPolicy() 拒绝策略代表新任务超过上限直接抛异常
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 10,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        MyRunnable myRunnable = new MyRunnable();

        //使用线程池来处理Runnable任务,Runnable是可以公用的,不需要New多个Runnable对象
        pool.execute(myRunnable);//提交第一个任务,因为第一次执行会创建线程处理任务
        pool.execute(myRunnable);//提交第二个任务,第二次不一定会创建新的线程,因为可能第一次的线程已经执行完了,会自动复用线程
        pool.execute(myRunnable);//提交第三个任务
        pool.execute(myRunnable);//提交第四个任务 测试线程复用
        pool.execute(myRunnable);//提交第五个任务 测试线程复用

        //关闭线程池(一般不关闭)
        pool.shutdown();//会等待所有线程执行完毕再进行关闭
        pool.shutdownNow();//立即关闭,会强制关闭正在执行的任务,并且不再接受新的任务

    }
}

线程池处理Callable任务代码

package com.example.demo.MyExecutor;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
    int n;
    public MyCallable(int n){
        this.n=n;
    }
    public String call() throws Exception {
        int sum =0;

        for (int i = 0; i < n; i++){
            sum += i;
        }
        return "子线程:"+Thread.currentThread().getName()+"执行结果,i="+sum;
    }
}

package com.example.demo.MyExecutor;

import java.util.concurrent.*;

public class ExecutorServiceDemo2 {
    public static void main(String[] args) {
        //使用线程池的实现类ThreadPoolExecutor创建线程池
        //new ArrayBlockingQueue<>(5)代表任务队列可以排队3个
        // new ThreadPoolExecutor.AbortPolicy() 拒绝策略代表新任务超过上限直接抛异常
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 10,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        //使用线程池来处理Runnable任务,Runnable是可以公用的,不需要New多个Runnable对象
        Future<String> submit1 = pool.submit(new MyCallable(100));
        Future<String> submit2 = pool.submit(new MyCallable(200));
        Future<String> submit3 = pool.submit(new MyCallable(300));
        Future<String> submit4 = pool.submit(new MyCallable(600));
        try{
            System.out.println(submit1.get());
            System.out.println(submit2.get());
            System.out.println(submit3.get());
            System.out.println(submit4.get());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }
}

临时线程创建与拒绝新任务时机

临时线程创建:
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
什么时候会拒绝新任务:
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。
任务拒绝策略:
|策略|说明|
|ThreadPoolExecutor.AbortPolicy()|丢弃任务并抛出RejectedExecutionException异常。是默认的策略|
|ThreadPoolExecutor. DiscardPolicy()|丢弃任务,但是不抛出异常,这是不推荐的做法|
|ThreadPoolExecutor. DiscardOldestPolicy()|抛弃队列中等待最久的任务 然后把当前任务加入队列中|
|ThreadPoolExecutor. CallerRunsPolicy()|由主线程负责调用任务的run()方法从而绕过线程池直接执行|

通过Executors工具类创建线程池

Executors是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。

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

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

使用可能存在的陷阱

大型并发系统环境中使用Executors如果不注意可能会出现系统风险。
阿里巴巴Java开发手册
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool和singleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM(内存溢出)。
2)CachedThreadPool和ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

线程池的核心线程和最大连接的设置

配置线程池的核心线程数量(corePoolSize)和最大线程数量(maximumPoolSize)时,没有固定的公式,但可以根据具体的应用场景和系统资源进行合理设置。
以下是一些建议:

  • 核心线程数 (corePoolSize):

    • 对于CPU密集型任务,可以将核心线程数设置为 CPU 核心数 + 1,以充分利用 CPU 资源。
    • 对于IO密集型任务,可以将核心线程数设置得更高,因为 IO 操作会阻塞线程,更多的线程可以提高并发处理能力。
  • 最大线程数 (maximumPoolSize):

    • 最大线程数通常设置为核心线程数加上一个合理的额外线程数,以应对突发的高负载情况。
    • 一般情况下,最大线程数可以设置为 2 * CPU 核心数 或更高,具体取决于系统的内存和 CPU 资源。
  • 队列大小 (workQueue):

    • 队列大小也会影响线程池的性能。如果队列大小设置得过大,可能会导致大量任务积压,占用大量内存。
    • 如果队列大小设置得过小,可能会导致频繁创建新线程,增加系统开销。

综合考虑以上因素,可以参考以下公式进行配置:

int cpuCores = Runtime.getRuntime().availableProcessors();
int corePoolSize = cpuCores + 1; // CPU 密集型任务
// int corePoolSize = 2 * cpuCores; // IO 密集型任务
int maximumPoolSize = 2 * cpuCores;

当然,上述这些只是建议值,实际应用中需要根据具体的业务需求和系统资源进行调整。

其他常见问题

Executors工具类底层是基于什么方式实现的线程池对象?
线程池ExecutorService的实现类:ThreadPoolExecutor
Executors是否适合做大型互联网场景的线程池方案?
不合适。建议使用ThreadPoolExecutor来指定线程池参数,这样可以明确线程池的运行规则,规避资源耗尽的风险

posted on 2024-12-04 21:28  饿得慌~  阅读(7)  评论(0编辑  收藏  举报