使用线程池的好处:
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
某些流程中的一些节点,由于是串联执行的。上一步要等下一步执行完毕;或者提交数据之后要等待后台其他系统处理完成之后,才能返回结果。这样就会导致,请求发起方不得不一直等待结果,用户体验很不好;从项目优化来说,模块与模块之间构成了强耦合,这也是不利于以后扩展的,更不用说访问量上来之后,肯定会抓瞎的问题。所以,我就着手开始,利用异步线程池来解决这个问题。
自定义一个线程池,以Bean的方式可注入到调用的地方
package com.rmb.order.config;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 线程池配置.
*
* @author cby
*/
@Configuration
public class ThreadPoolConfig {
@Value("${thread-pool.syncOrderCustomerPool.coreSize}")
private int syncOrderCustomerPoolCoreSize;
@Value("${thread-pool.syncOrderCustomerPool.maxSize}")
private int syncOrderCustomerPoolMaxSize;
@Value("${thread-pool.syncOrderCustomerPool.queueCapacity}")
private int syncOrderCustomerPoolQueueCapacity;
/**
* 同步订单客户线程池
*
* @return ThreadPoolExecutor
*/
@Bean("syncOrderCustomerExecutor")
public ThreadPoolExecutor syncOrderCustomerExecutor() {
Preconditions.checkArgument(
syncOrderCustomerPoolCoreSize > 0 && syncOrderCustomerPoolMaxSize > 0
&& syncOrderCustomerPoolQueueCapacity > 0,
"thread-pool.syncOrderCustomerPool not set in application.yml");
return new ThreadPoolExecutor(syncOrderCustomerPoolCoreSize, syncOrderCustomerPoolMaxSize,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<>(syncOrderCustomerPoolQueueCapacity),
new ThreadFactoryBuilder().setNameFormat("order-customer-thread-pool-%d").build(),
new ThreadPoolExecutor.AbortPolicy());
}
}
在同步的过程中通过线程池完成异步操作
这样在下单的时候不用等待同步订单客户的时间
/**
* 下单
*
**/
@Override
@Transactional(rollbackFor = Exception.class)
public PlaceOrderRes placeOrder(PlaceOrderRequest placeOrderRequest) throws Exception {
...
// 同步订单客户到uc2c
if (memberId != null && memberId > 0) {
syncOrderCustomer(storeResponse.getOrgId(), memberId);
}
return placeOrderRes;
}
@Autowired
@Qualifier("syncOrderCustomerExecutor")
private ThreadPoolExecutor syncOrderCustomerExecutor;
/**
* 同步订单客户
*
* @param orgId 运营商id
* @param memberId 成员id
*/
private void syncOrderCustomer(long orgId, long memberId) {
syncOrderCustomerExecutor.execute(() -> {
try {
Customer2cClientUtil.createOrUpdateOrderCustomer(orgId, memberId);
} catch (Exception ex) {
log.warn("同步订单客户异常!", ex);
}
});
}
在Spring3.x之后框架已经支持采用@Async注解进行异步执行了。
被@Async修饰的方法叫做异步方法,这些异步方法会在新的线程中进行处理,不影响主线程的顺序执行。
关于线程池,它也可避免系统频繁创建销毁线程,就像数据库连接池一样
java.uitl.concurrent.ThreadPoolExecutor是线程池最核心类,
public class ThreadPoolExecutor extends AbstractExecutorService {
.....
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
...
}
解释下构造器中各参数含义
corePoolSize:核心池大小。通常情况线程池没有任何线程,等有任务来才会创建线程执行任务。当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行;
maximumPoolSize:线程池最大线程数。当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理;
keepAliveTime:线程没有执行任务时保持多久时间会终止;
unit:keepAliveTime的时间单位;
workQueue:阻塞队列,存储等待执行的任务;
threadFactory:主要用来创建线程;
handler:拒绝处理任务策略。
ThreadPoolExecutor有几个重要的方法execute()、submit()、shutdown()、shutdownNow():
execute()可向线程池提交一个任务,交由线程池执行,submit()同样,但能返回任务执行结果;
剩下两个方法用来关闭线程池。
虽然线程池是构建多线程应用程序的强大机制,但使用它并不是没有风险的。用线程池构建的应用程序容易遭受任何其它多线程应用程序容易遭受的所有并发风险,诸如同步错误和死锁,它还容易遭受特定于线程池的少数其它风险,诸如与池有关的死锁、资源不足和线程泄漏。
常见几种线程池
CachedThreadPool
是通过java.util.concurrent.Executor创建的ThreadPoolExecutor实例
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
需要注意的是使用的队列是SynchronousQueue直接提交队列,没有容量,每插入一个操作都要等待一个相应的删除操作。直接将任务提交给线程执行,若没有空闲线程则创建线程,直到进程数量达到最大值,执行拒绝策略。但CachedThreadPool最大线程数MAX_VALUE,无穷大线程池,它总会迫使线程池增加新的线程执行任务。空闲线程会在指定时间(60秒)被回收。
若同时有大量任务被提交,而且任务执行又不快,那么就会开启大量线程,这样会很快耗尽系统资源。
FixedThreadPool
也是ThreadPoolExecutor实例
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
这里使用的是LinkedBlockingQueue无界任务队列,除非资源耗尽,否则不存在任务入队失败。线程数小于corePoolSize时,线程池会生成新的线程执行任务,达到corePoolSize时,任务直接进入队列等待,队列会保持快速增长,直到耗尽内存。可看到corePoolSize与maximumPoolSize相等,可自定义线程池线程数。
SingleThreadPool
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
还有有界任务队列ArrayBlockingQueue
同时还可自定义创建线程池
ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>());
FixedThreadPool 和 CachedThreadPool 两者对高负载的应用都不是特别友好。
CachedThreadPool 要比 FixedThreadPool 危险很多。
如果应用要求高负载、低延迟,最好不要选择以上两种线程池:
- 任务队列的无边界:会导致内存溢出以及高延迟
- 长时间运行会导致
CachedThreadPool
在线程创建上失控
因为两者都不是特别友好,所以推荐使用 ThreadPoolExecutor
,它提供了很多参数可以进行细粒度的控制。
线程池的拒绝策略
/**
* Handler called when saturated or shutdown in execute.
*/
private volatile RejectedExecutionHandler handler;
/**
* A handler for tasks that cannot be executed by a {@link ThreadPoolExecutor}.
*
* @since 1.5
* @author Doug Lea
*/
public interface RejectedExecutionHandler {
/**
* Method that may be invoked by a {@link ThreadPoolExecutor} when
* {@link ThreadPoolExecutor#execute execute} cannot accept a
* task. This may occur when no more threads or queue slots are
* available because their bounds would be exceeded, or upon
* shutdown of the Executor.
*
* <p>In the absence of other alternatives, the method may throw
* an unchecked {@link RejectedExecutionException}, which will be
* propagated to the caller of {@code execute}.
*
* @param r the runnable task requested to be executed
* @param executor the executor attempting to execute this task
* @throws RejectedExecutionException if there is no remedy
*/
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
接口有一个rejectedExecution()方法,具体到不同的拒绝策略
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
ThreadPoolExecutor.AbortPolicy上面这种是直接抛出异常,任务终止
/**
* Executes task r in the caller's thread, unless the executor
* has been shut down, in which case the task is discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
ThreadPoolExecutor.CallerRunsPolicy这种是只要线程池没关闭,调用线程处理任务
/**
* Obtains and ignores the next task that the executor
* would otherwise execute, if one is immediately available,
* and then retries execution of task r, unless the executor
* is shut down, in which case task r is instead discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
ThreadPoolExecutor.DiscardOldestPolicy丢弃队列最前面的任务,就是最老的请求,然后重新尝试执行任务(重复此过程)
/**
* Does nothing, which has the effect of discarding task r.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
ThreadPoolExecutor.DiscardPolicy丢弃任务,不做任何处理
也可自定义拒绝策略,自己扩展RejectExecutionHandler接口:
ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(10),
Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString() + " is discard");
}
});
上面就是定义5个常驻线程,最大线程数也是5个,10个容量的等待队列,超过核心线程数后的线程存活时间0秒,默认的线程工厂,以及只打印日志的线程策略。
上面提及线程工厂ThreadFactory,池子中的线程是从哪来的?答案就是它,当需要新建线程时,就会调用他的创建线程方法。
ThreadFactory是只有一个创建线程方法的接口,也可自定义。我们可利用它自定义线程名称、组以及优先级,甚至可将线程设置为守护线程。总之可让我们更自由的设置池子中线程的状态。
ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(10),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread();
t.setDaemon(true);
return t;
}
});
上面就是自定义线程池, 将池子中创建的线程都设置为守护线程。
ThreadFactoryBuilder 可设置线程优先级,守护线程,线程名字等
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.google.common.util.concurrent;
import com.google.common.base.Preconditions;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;
public final class ThreadFactoryBuilder {
private String nameFormat = null;
private Boolean daemon = null;
private Integer priority = null;
private UncaughtExceptionHandler uncaughtExceptionHandler = null;
private ThreadFactory backingThreadFactory = null;
public ThreadFactoryBuilder() {
}
public ThreadFactoryBuilder setNameFormat(String nameFormat) {
String.format(nameFormat, new Object[]{Integer.valueOf(0)});
this.nameFormat = nameFormat;
return this;
}
public ThreadFactoryBuilder setDaemon(boolean daemon) {
this.daemon = Boolean.valueOf(daemon);
return this;
}
public ThreadFactoryBuilder setPriority(int priority) {
Preconditions.checkArgument(priority >= 1, "Thread priority (%s) must be >= %s", new Object[]{Integer.valueOf(priority), Integer.valueOf(1)});
Preconditions.checkArgument(priority <= 10, "Thread priority (%s) must be <= %s", new Object[]{Integer.valueOf(priority), Integer.valueOf(10)});
this.priority = Integer.valueOf(priority);
return this;
}
public ThreadFactoryBuilder setUncaughtExceptionHandler(UncaughtExceptionHandler uncaughtExceptionHandler) {
this.uncaughtExceptionHandler = (UncaughtExceptionHandler)Preconditions.checkNotNull(uncaughtExceptionHandler);
return this;
}
public ThreadFactoryBuilder setThreadFactory(ThreadFactory backingThreadFactory) {
this.backingThreadFactory = (ThreadFactory)Preconditions.checkNotNull(backingThreadFactory);
return this;
}
public ThreadFactory build() {
return build(this);
}
private static ThreadFactory build(ThreadFactoryBuilder builder) {
final String nameFormat = builder.nameFormat;
final Boolean daemon = builder.daemon;
final Integer priority = builder.priority;
final UncaughtExceptionHandler uncaughtExceptionHandler = builder.uncaughtExceptionHandler;
final ThreadFactory backingThreadFactory = builder.backingThreadFactory != null?builder.backingThreadFactory:Executors.defaultThreadFactory();
final AtomicLong count = nameFormat != null?new AtomicLong(0L):null;
return new ThreadFactory() {
public Thread newThread(Runnable runnable) {
Thread thread = backingThreadFactory.newThread(runnable);
if(nameFormat != null) {
thread.setName(String.format(nameFormat, new Object[]{Long.valueOf(count.getAndIncrement())}));
}
if(daemon != null) {
thread.setDaemon(daemon.booleanValue());
}
if(priority != null) {
thread.setPriority(priority.intValue());
}
if(uncaughtExceptionHandler != null) {
thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
}
return thread;
}
};
}
}