JAVA线程池的使用

一、使用 Executors 创建线程池

Executors是一个线程池工厂类,里面有许多静态方法,供开发者调用。

/* 该方法返回一个固定线程数量的线程池,该线程池池中的线程数量始终不变。
 * 当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。
 * 若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务 
 * 默认等待队列长度为Integer.MAX_VALUE
 */
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1);

/* 该方法返回一个只有一个线程的线程池。
 * 若多余一个任务被提交到线程池,任务会被保存在一个任务队列中,等待线程空闲,按先入先出顺序执行队列中的任务
 * 默认等待队列长度为Integer.MAX_VALUE
 */
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

/* 
 * 该方法返回一个可根据实际情况调整线程数量的线程池。
 * 线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。
 * 若所有线程均在工作,又有新任务的提交,则会创建新的线程处理任务。
 * 所有线程在当前任务执行完毕后,将返回线程池进行复用
 */
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();

/* 该方法返回一个ScheduledExecutorService对象,线程池大小为1。
 * ScheduledExecutorService接口在ExecutorService接口之上扩展了在给定时间内执行某任务的功能,
 * 如在某个固定的延时之后执行,或者周期性执行某个任务
 */
ExecutorService newSingleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();

/*
 * 该方法也返回一个ScheduledExecutorService对象,但该线程池可以指定线程数量
 */
ExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(1);

Executors 的静态方法都是基于 ThreadPoolExecutor 类实现的,相当于 ThreadPoolExecutor 的 语法糖。

但这几个静态方法都存在一个弊端,因为会在创建线程池的同时隐式创建等待队列,而队列的长度默认是 Integer.MAX_VALUE ,相当于不限长度,这样就存在OOM的隐患。

二、使用 ThreadPoolExecutor 创建线程池

上面说过, Executors 的静态方法都是基于 ThreadPoolExecutor 类实现的,所以在生产环境下,还是建议直接使用 ThreadPoolExecutor 类创建线程池:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue);

ThreadPoolExecutor 有多个构造方法,一般来说使用最精简的即可。

三、参数含义

corePoolSize

指定线程池的核心线程数。

当一个新任务被添加到线程池时,首先会判断当前的线程数(ThreadCount),如果:

A:ThreadCount < corePoolSize:即当前线程数小于核心线程数,就会创建一个新的线程来执行这个任务;

B:ThreadCount >= corePoolSize:即当前线程数大于等于核心线程数,就会将新任务添加到等待队列中。

该参数的两个特殊参数值:

1、0: 意味着没有核心线程,全部线程都会受到 keepAliveTime 参数的回收机制影响。

2、Integer.MAX_VALUE:意味着不限制核心线程数,连等待队列都不需要,可以想象这种情况下很容易OOM。

maximumPoolSize

指定线程池的最大线程数,包括核心线程和非核心线程。

当另一个新任务被添加到线程池时,如果此时等待队列的容量已满,则会判断当前的线程数(ThreadCount),如果:

A:ThreadCount < maximumPoolSize:即当前线程数小于最大线程数,就会创建一个新的线程来执行这个任务;

B:ThreadCount == maximumPoolSize:即当前线程数已达到最大值,此时等待队列的容量也已用尽,因此会抛出异常。

该参数的两个特殊参数值:

1、0: 意味着只有核心线程,默认情况下全部线程都不会受到 keepAliveTime 参数的回收机制影响,除非设置 allowCoreThreadTimeOut 为 true。

2、Integer.MAX_VALUE:意味着不限制最大线程数,这种情况下也很容易OOM。

keepAliveTime

空闲线程的存活时间。

默认情况下,该参数只对非核心线程有效。

在处理大量任务时,可能会创建大量的非核心线程,在所有任务都执行完成后会继续保留这些非核心线程一段时间,等时间到了就会自动回收,以减少系统开销。

当设置线程池的 allowCoreThreadTimeOut(true) 时,意味着该参数也同时对核心线程有效,在时间到了之后,全部线程都会自动回收。

unit

空闲线程存活时间的单位。

workQueue

等待队列。

创建线程池时另外一个容易引起OOM的重要参数,主要包括以下几种:

1、ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。

2、LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按 FIFO(先进先出)排序元素,吞吐量通常要高于 ArrayBlockingQueue。静态工厂方法 Executors.newFixedThreadPool() 使用了这个队列。

3、SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQueue,静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。

4、PriorityBlockingQueue:一个具有优先级的无限阻塞队列。

以最常用的 LinkedBlockingQueue 为例:

//创建一个容量为9999的队列实例
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(9999);

关于线程池各参数的作用,可以通过下面的图片进行详细了解:

四、使用线程池的注意事项

一句话:应该最大化的,同时也要有限度的满足业务需求。

在实际使用线程池时,首先应该确保所创建的线程池可以满足业务设计需求,主要就是线程数和队列容量,前者由CPU核心数限制,后者由服务器内存限制。

线程太少,则消费队列的时间就长,就需要更大容量的队列;线程太多,会增加大量的上下文切换时间,反而不利于合理分配CPU的计算资源。

队列太小,则添加任务时可能会抛出异常;队列太大,会占用更多的内存消耗。

关键是切勿使用无边界值(Integer.MAX_VALUE),这也是造成OOM的最主要原因。

可以根据服务器配置和业务需求,对这两个方面进行均衡考虑。

五、使用案例

int cpuCoreCnt = Runtime.getRuntime().availableProcessors(); //获取服务器CPU核心数
int corePoolSize = cpuCoreCnt;      // 核心线程数
int maximumPoolSize = cpuCoreCnt;   // 最大线程数
int keepAliveTime = 30;             // 非核心线程的空闲存活时长(分钟)
int queueCapacity = 9999;           // 队列最大长度

BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(queueCapacity);
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MINUTES, queue);
threadPool.allowCoreThreadTimeOut(true);    //允许回收核心线程

上面案例中,使用CPU核心数作为最大线程数,相对来说还是比较合理的。

等待队列的容量尽可能设置的大一些,和添加任务时抛出异常相比,多付出一些内存来实现更大容量的队列还是非常值得的。

keepAliveTime 也可以适当设置的长一些,避免太快回收,毕竟频繁的创建线程也是需要时间开销的。

最后还设置了allowCoreThreadTimeOut方法,允许自动回收核心线程,用来减少阻塞线程的性能消耗。

六、线程池复用

线程池在完成全部的任务后,会自动进入摸鱼状态,期间会根据配置自动回收空闲线程,直到新的任务被添加进来再起来工作。

即使设置了 allowCoreThreadTimeOut(true) 对核心线程进行回收,有新任务时也会重新创建核心线程继续进入工作状态。

只要不是 调用 shutdown() 手动关闭它,正常情况下线程池是可以长期重复性使用的。

有些强迫症患者(比如本人)会非常介意一个无所事事的线程池在内存里装死,因此必须手动 shutdown 才会安心。

但这样的话,之前的线程池就彻底挂掉了,再向其中添加任务时会抛出异常。

有效的做法是,将创建线程池的部分单独封装,每次添加任务时都进行判断,如果当前线程池已经挂掉了,就重新创建一个:

/**
 * <p>
 * 添加任务
 * 注:如果线程池已关闭,会自动创建新的线程池
 * </p>
 * 
 * @param task
 */
public void addTask(Task task){
	if(threadPool.isShutdown()) createThreadPool(corePoolSize, maximumPoolSize, keepAliveTime);
	threadPool.execute(task);
}

 

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java线程池提供了一种线程复用的机制,以及一些控制和管理线程的方法。它可以帮助我们更好地管理线程,提高应用程序的性能。 以下是使用Java线程池的步骤: 1. 创建线程池对象 可以使用Executors类提供的静态方法来创建线程池对象,如下所示: ``` ExecutorService executor = Executors.newFixedThreadPool(10); ``` 这里使用了newFixedThreadPool方法来创建一个固定大小的线程池,最多可以同时执行10个任务。 2. 提交任务 将任务提交给线程池,如下所示: ``` executor.execute(new Runnable() { public void run() { // 执行任务的代码 } }); ``` 这里使用了execute方法将任务提交给线程池。你可以将任务封装在Runnable接口的实现类中,并将其作为参数传递给execute方法。 3. 关闭线程池 当你的应用程序不再需要线程池时,应该关闭它,以释放资源。可以使用shutdown方法来关闭线程池,如下所示: ``` executor.shutdown(); ``` 这里使用了shutdown方法来关闭线程池。调用shutdown方法后,线程池将不再接受新的任务,但会等待所有已提交的任务执行完毕,然后关闭线程池。 除了newFixedThreadPool方法外,Java线程池还提供了其他的线程池类型,如newCachedThreadPool、newSingleThreadExecutor等,可以根据需要选择合适的线程池类型。同时,线程池还提供了一些控制和管理线程的方法,如setCorePoolSize、setMaximumPoolSize、setKeepAliveTime等,可以根据需要进行配置。 ### 回答2: Java线程池是用来管理和利用线程的一种机制。它可以有效地将有限的资源(线程)进行分配和复用,提高程序的性能和资源利用率。 使用线程池可以带来一系列好处。首先,线程池能够控制并发的线程数量,避免过多的线程导致系统资源的浪费。其次,线程池可以重复利用已创建的线程,避免频繁地创建和销毁线程的开销。再次,线程池可以对线程进行管理,比如设置线程的优先级、超时时间等,提高了线程的效率。最后,线程池还可以提供线程的监控和统计信息,方便进行系统性能的调优。 在Java中,线程池一般会使用Executor框架来实现。常见的线程池实现类有ThreadPoolExecutor和ScheduledThreadPoolExecutor。 通过ThreadPoolExecutor类,我们可以创建一个线程池,并指定线程的初始大小、最大大小、线程空闲时的存活时间等参数。我们可以将任务提交给线程池执行,线程池会自动选择一个可用的线程来执行任务。当任务执行完毕后,线程会返回线程池继续等待下一个任务的分配。 使用线程池时,应该根据具体情况来配置线程池的参数。如果任务量较大,可以适当增加线程池的大小,以充分利用系统资源。如果任务量较小,可以适当减少线程池的大小,避免资源的浪费。同时,应该注意合理设置线程的优先级和超时时间,以保证任务的顺利执行。 总之,Java线程池使用能够提高程序的性能和资源利用率,减少线程的创建和销毁开销,对于多线程编程是非常有益的工具。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值