java线程池详解

本文详细介绍了Java中自定义ThreadFactory、ThreadPool和ExecutorService的使用,包括线程池的创建方式、参数分析、队列选择、线程泄露问题以及不同队列的特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java多线程关于ThreadPool

一、自定义ThreadFactory和线程池的工厂类Executors

自定义ThreadFactory,可以自定义线程的名字

public class MyThreadFactory implements ThreadFactory {

    private final String prefix = "async-task-";
    private final AtomicInteger threadNumber = new AtomicInteger(1);

    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(null,r,prefix + threadNumber.getAndIncrement());
        return thread;
    }
}

Executors可以用来创建线程池,但是不太推荐。
可以使用ThreadPoolExecutor来自定义线程池,来适用当前业务

Executors.newCachedThreadPool:

创建的是无界线程池(可以创建的线程数量是Integer.MAX_VALUE),缺点:高并发下内存占用率大幅升高,导致内存溢出或者系统性能严重下降。

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
Executors.newFixedThreadPool:

创建时需要传入核心线程池的线程数,并使用LinkedBlockingQueue作为排队队列,创建的线程数是有限的。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
Executors.newSingleThreadExecutor

核心线程只有一个,其它提交的线程全部放入队列等待执行。

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

二、ThreadPoolExecutor类的参数和使用

2.1、参数介绍:

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                KeepaliveTime,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(2),
                new MyThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

corePoolSize: 线程池中至少要保留的线程,核心线程默认是不会超时销毁的。pool.allowCoreThreadTimeOut(true),可以开启核心线程超时销毁。

maximumPoolSize:可创建的最大线程数。

KeepaliveTime:当线程数量大于corePoolSize,没有超过指定的时间是不能从线程池中将线程删除的。范围是maximumPoolSize~corePoolSize之间的线程。是核心线程之外的线程

unit:KeepaliveTime参数的时间单位

线程队列:
1、使用linkedBlockingQueue做队列,linkedBlockingQueue是有界的,默认Integer.MAX_VALUE
2、ArrayBlockingQueue队列。实例化时必须传入初始容量,并且容量不可扩充,超出初始容量会报错。
3、SynchronousQueue队列,该队列不存储数据,通过该队列,可以在两个线程之间传递数据

WorkQueue:执行任务的任务队列,保存有executor提交的任务

2.2、线程池的队列类型

使用 LinkedBlockingQueue<>()无界队列的情况

1、对于存在corePoolSIze和无参new LinkedBlockingQueue<>()队列的情况下,无论提交了多少个任务,线程池中的线程数都不会超过corePoolSize。

2、对于提交的线程数大于corePoolSIze的情况,只会创建corePoolSIze个线程,其他的任务全部放入队列中的等待执行。

使用 LinkedBlockingQueue<>(n)有界队列的情况

1、对于提交的线程数大于maximumPoolSize + n 的情况,线程池中会立即创建并不多于maximumPoolSize个线程来执行任务,多于maximumPoolSize+ n的额外任务会被拒绝并抛出异常。

使用SynchronousQueue<>()的情况

1、如果提交的线程数量大于corePoolSize,而小于等于maximumPoolSize,则创建最多不超过maximumPoolSize个线程来执行任务,如果空闲线程存活时间超过keepaliveTime,则超过corePoolSize之外的线程会被杀死,其他线程依然存活。

2、如果提交任务数量大于maximumPoolSize,则抛出异常。

2.3、线程池的拒绝策略

1、AbortPolicy

当任务添加到线程池被拒绝时,直接抛出RejectedExceutionException异常,是线程池的默认策略。

2、CallerRunsPolicy

被拒绝后,会被加入到调用线程池的线程去处理,可能会造成主线程的阻塞。
比较好的处理方法是在主线程中创建一个线程,并由该线程负责线程池的调用。

3、DiscardOldestPolicy

当任务添加线程池被拒绝时,线程池会放弃等待队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列。

4、Discardpolicy

当任务添加到线程池中被拒绝时,线程池会丢弃被拒绝的任务。

三、ThreadPoolExecutor 其他的一些方法

afterExecute()和beforeExecute()

重写这两个方法可以对线程池的线程对象实现监控。

remove()方法

对于execute 提交的任务,remove方法可以删除未运行的thread,但是对于正在运行的thread无法删除。

对于submit 提交的任务,remove方法不能删除。

execute方法: 返回void,只能提交Runnable类型任务

submit方法: 可以提交Runnable和Callable类型的任务,当提交Runnable时返回值是null,提交的类型是callable时,返回的类型是Future。

submit和submit方法对于异常处理的区别:

submit方法是是捕获了异常的,当调用FutureTask的get方法时,才会抛出异常。运行时出现异常不会抛出。

但是execute方法,在运行时有异常就会抛出。

四、工作队列、线程池大小的选择

工作队列

工作队列可以使用有界队列,也可以使用无界队列,或者直接交接队列(SynchronousQueue)。

对于无界队列,虽然无界队列本身不限制线程池任务数,但是无界队列的容量还是取决于任务本身对资源的使用情况。如果创建任务时引用了其他的对象,而这些对象又特别大,随着队列中任务越多,会导致这些对象占用的内存也越来越多。极端情况下可能会导致内存溢出。

可以对队列的大小做出限制,或者使用直接交接队列SynchronousQueue,那么便会创建不多于最大线程数的线程,且又不会无限的缓存任务。

线程池大小

cpu密集型:

可以考虑N+1,考虑+1是可能会有缺页中断,导致线程等待。

io密集型:

可以设置的相对大一点,因为等待io的时间会比使用cpu的时间长,处于io等待状态的线程并不会消耗cpu资源,例如2n。

对于执行io操作的线程越多,引起上下文切换也就越多,因此对于io密集型的任务,核心线程数可以设为1,最大线程数可以设为2N,这样导致的上下文切换也是最少的。

五、线程泄露

run方法没有异常捕获,导致run方法意外返回,使线程提前终止。

或者run方法一直处于等待状态,没有设置超时。

六、队列

6.1 非阻塞队列

当队列为空时返回异常或者null
ConcurrentHashMap:支持并发操作的map,不支持排序。

ConcurrentSkipListMap:支持并发操作,且支持排序。需要对象实现Comparable接口,并重写compareTo方法,实现排序,返回-1表示小,返回1表示大,返回0表示相等。

ConcurrentSkipListSet:支持并发和排序,且不允许数据重复。需要对象实现Comparable接口,并重写compareTo方法,实现排序,返回-1表示小,返回1表示大,返回0表示相等。重写hashCode方法,和equals。

ConcurrentLinkedQueue:提供了并发的队列,仅支持对头的操作。

ConcurrentLinkedDequeue:支持对头尾的双向操作。

CopyOnWriteArrayList:由于ArrayList是线程不安全的,可以使用它代替。

CopyOnWriteArraySet:可以解决HashSet不安全的问题。

6.2阻塞队列

当队列为空,或者队列的容量满的时候,会使线程等待,直到队列中有数据或者队列有空间的时候,线程会被唤醒。

ArrayBlockingQueuq:没有空间时,put()存放数据会阻塞,take()取数据时,队列为空,会阻塞。
new ArrayBlockQueue(10,true);可以使用公平锁和非公平锁。true为公平锁,false为非公平锁。

LinkedBlockingQueue:和ArrayBlockingQueue大体上一样,但是ArrayBlockingQueue要比LinkedBlockingQueue效率高。LinkedBlockingQueue支持队列头部的操作。

SynchronousQueue:是一种阻塞队列,没有容量。

### Java 线程池详解 #### 创建线程池的方式 Java 提供了几种创建线程池的方法,最常用的是通过 `Executors` 工厂类来获取预配置好的线程池实例。然而,在实际项目中更推荐使用 `ThreadPoolExecutor` 构造器来自定义参数,以获得更好的灵活性和性能控制[^1]。 ```java // 不推荐的做法:固定大小的缓存线程池可能导致资源浪费或耗尽 ExecutorService cachedPool = Executors.newCachedThreadPool(); // 推荐做法:自定义 ThreadPoolExecutor 参数设置 ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build(); ThreadPoolExecutor executor = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workQueue, namedThreadFactory); ``` #### 关键组件说明 - **核心线程数 (`corePoolSize`)** 定义了即使处于空闲状态也会被保留在线程池中的最小线程数量。 - **最大线程数 (`maximumPoolSize`)** 设置允许的最大活动线程数目;当现有任务队列已满而又有新的提交任务时才会触发扩容至该上限。 - **存活时间 (`keepAliveTime`)** 非核心线程闲置后的自动回收等待周期长度。 - **阻塞队列 (`workQueue`)** 存储待处理的任务对象集合,不同类型的队列会影响吞吐量表现及拒绝策略行为。 - **线程工厂 (`threadFactory`)** 负责生产新线程实体,默认实现较为简单,通常建议开发者根据应用场景定制化命名规则以便于调试跟踪。 #### 常见错误与优化技巧 忽视对线程池内部运行状况监控是常见的失误之一。应当定期审查并调整相关参数,确保其适应当前负载需求变化趋势。另外需要注意捕获未预见异常情形下的恢复机制设计,防止因个别失败案例影响整体服务稳定性[^2]。 #### 实践指南 对于Web容器如Tomcat而言,内置有经过优化过的专用线程管理模块用于支撑高并发请求场景下高效运作的要求。尽管如此,了解基础概念仍然有助于更好地理解框架底层运作机理,并能在必要时候做出针对性调优措施。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值