转自: http://heqiao2010.com/articles/2018/06/22/1529662276856.html
目前在公司做的一个无线Wi-Fi认证系统,采用公有云模式,24小时不间断服务,而且在上班时间会有业务并发的高峰,目前高峰值能到4000多的qps,在这个领域来说,还是比较高的。在这种场景下需要将一些操作异步执行,以提高页面的响应速度,比如某些情况下将大对象入库,可以采用异步线程去处理,这样在入库没有完成时,请求就可以返回(前提是入库失败,不需要通知给客户端)。那么如何创建异步线程,去执行这种异步操作,在保证效率的同时,还不能被高并发冲垮呢?
异步操作的几种实现方式
直接创建一个新线程
继承或者实现Runnable接口都可以创建一个异步子线程。
public class TestThread1 extends Thread{
private boolean flag = true;
public static void main( String args[])
{
TestThread1 t = new TestThread1();
t.start();
for( int i=0; i<100; i++)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("I'm MainThread.");
}
t.endSubThread();
System.out.println("MainThread Stoped.");
}
public void run()
{
while( this.flag )
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("----I'm SubThread.");
}
System.out.println("----SubThread Stoped.");
}
public void endSubThread()
{
this.flag = false;
}
}
直接创建子线程可能会导致高并发请求时,创建线程耗费额外的时间,拖慢响应速度,也可能导致创建的线程数过多导致OOM,并且这种线程是一次性的,不能重用;这种方式,我们一开始就没有采用。
采用固定大小的线程池
线程池大小固定,避免请过多线程出现OOM的问题;而且线程可重用,以提升效率。如果并发数超过线程池大小,则任务会缓存到一个队列中。
package com.heqiao2010;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by h12111 on 2018/6/23.
*/
public class FixedThreadPool {
/**
* 当前线程池中线程数量
*/
private AtomicInteger currentThreadNum = new AtomicInteger(0);
/**
* 线程池大小
*/
private static final Integer POOLSIZE = 8;
private volatile ExecutorService executor = null;
public FixedThreadPool(){
this(POOLSIZE);
}
public FixedThreadPool(Integer poolSize){
this.executor = Executors.newFixedThreadPool(poolSize, new ThreadFactory(){
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("FixedThreadPool" + currentThreadNum.incrementAndGet());
thread.setDaemon(true);
System.out.println(currentThreadNum.get() + " thread was created.");
return thread;
}
});
}
public void execute(Runnable task){
this.executor.submit(task);
}
}
测试:
private static AtomicInteger sum = new AtomicInteger(1000);
public static void main(String args[]){
FixedThreadPool threadPool = new FixedThreadPool(100);
for(int i=0; i<111; i++)
threadPool.execute(() ->{
while(sum.get() > 0){
System.out.println(Thread.currentThread().getName() + ": TestSum is " + sum.get());
try{
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
sum.decrementAndGet();
}
});
// 保证主线程不退出
while(sum.get() > 0){
System.out.println("Not finish Yet!");
try{
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
部分输出如下,当一次性提交111个任务时,只创建了100个线程。
97 thread was created.
FixedThreadPool96: TestSum is 1000
98 thread was created.
FixedThreadPool97: TestSum is 1000
99 thread was created.
FixedThreadPool98: TestSum is 1000
100 thread was created.
FixedThreadPool99: TestSum is 1000
Not finish Yet!
FixedThreadPool100: TestSum is 1000
FixedThreadPool2: TestSum is 997
线程池优化——采用丢弃策略的线程池
查看Executors.newFixedThreadPool
的源码会发现,ThreadPoolExecutor
的构造参数中传入了一个阻塞式任务队列,而这个队列居然是没有限制大小的。因此,当高并发时,过多的任务不会使系统创建过多的线程,但是都会堆积在队列中,这样同样可能会导致OOM。实际上,采用上面的实现方式,我们用Jmeter做压力测试的时候,就出现溢出了,服务挂了,且不能自愈,于是做了下面的优化。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(), //这个队列的最大长度可以是:Integer.MAX_VALUE
threadFactory);
}
改进如下,对于在阻塞队列满了之后的任务,可以采取丢弃处理的策略,保证服务本身的安全。
package com.heqiao2010;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 一个可以丢弃任务的线程池
* Created by heqiao on 2018/6/22.
*/
public class DiscardableThreadPool {
/**
* 当前线程池中线程数量
*/
private AtomicInteger currentThreadNum = new AtomicInteger(0);
private volatile ExecutorService executor = null;
/**
* 任务丢弃策略</>
*/
public static class DiscardPolicy implements RejectedExecutionHandler{
public DiscardPolicy(){
}
/**
* 只是简单的打印了个日志
* @param r
* @param executor
*/
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("Task " + r.toString() + " rejected from " + executor.toString());
}
}
public static class Builder {
/**
* 线程名前缀
*/
private String threadNamePrefix = "DiscardableThreadPool";
/**
* 线程池中最小线程数
*/
private Integer corePoolSize = 16;
/**
* 最大线程数
*/
private Integer maximumPoolSize = 128;
/**
* 线程超时回收时间
*/
private Long keepAliveTime = 5L;
/**
* 线程超时回收时间单位
*/
private TimeUnit timeUnit = TimeUnit.MINUTES;
/**
* 任务队列大小
*/
private Integer queueCapacity = 500;
/**
* 丢弃策略
*/
private RejectedExecutionHandler handler = new DiscardPolicy();
public Builder namePrefix(String prefix){
this.threadNamePrefix = prefix;
return this;
}
public Builder minPoolSize(int minPoolSize){
this.corePoolSize = minPoolSize;
return this;
}
public Builder maxPoolSize(int maxPoolSize){
this.maximumPoolSize = maxPoolSize;
return this;
}
public Builder keepAlive(long keepAliveTime, TimeUnit unit){
this.keepAliveTime = keepAliveTime;
this.timeUnit = unit;
return this;
}
public Builder queueCapacity(int capacity){
this.queueCapacity = capacity;
return this;
}
public Builder rejectedHanlder(RejectedExecutionHandler handler){
this.handler = handler;
return this;
}
public DiscardableThreadPool build(){
return new DiscardableThreadPool(this);
}
}
/**
* 构造子
* @param builder
*/
private DiscardableThreadPool(Builder builder){
if(null == executor){
// 线程工厂
ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName(builder.threadNamePrefix + currentThreadNum.incrementAndGet());
thread.setDaemon(true);
System.out.println(currentThreadNum.get() + " thread was created.");
return thread;
}
};
// 任务队列
BlockingQueue<Runnable> workQueue = new LinkedBlockingDeque<Runnable>(builder.queueCapacity);
// 初始化
executor = new ThreadPoolExecutor(builder.corePoolSize, builder.maximumPoolSize, builder.keepAliveTime, builder.timeUnit,
workQueue, threadFactory, builder.handler);
}
}
/**
* 执行一个异步任务
* @param task
*/
public void execute(Runnable task){
executor.submit(task);
}
@Override
public String toString() {
return "DiscardableThreadPool{" +
"executor=" + executor +
'}';
}
public boolean isShutDown(){
return executor.isShutdown();
}
public boolean isTeminated(){
return executor.isTerminated();
}
}
测试代码:
package com.heqiao2010;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 测试!
*/
public class Main {
private static AtomicInteger sum = new AtomicInteger(1000);
public static void main(String[] args) {
//最多100个线程,队列大小是10个,理论上最大并发支持到110
DiscardableThreadPool threadPool =
new DiscardableThreadPool.Builder().minPoolSize(8)
.maxPoolSize(100).keepAlive(1, TimeUnit.MINUTES)
.namePrefix("Test").queueCapacity(10)
.build();
//一次性提交111个任务
for(int i=0; i<111; i++)
threadPool.execute(() ->{
while(sum.get() > 0){
System.out.println(Thread.currentThread().getName() + ": TestSum is " + sum.get());
try{
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
sum.decrementAndGet();
}
});
// 保证主线程不退出
while(sum.get() > 0){
System.out.println("Not finish Yet!");
try{
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
部分输出如下,可见在线程池满了,而且队列也满了的情况下,任务就会被丢弃掉了。
Test98: TestSum is 1000
100 thread was created.
Test99: TestSum is 1000
Test77: TestSum is 1000
Test68: TestSum is 1000
Test72: TestSum is 1000
Test76: TestSum is 1000
Test80: TestSum is 1000
Test84: TestSum is 1000
Task java.util.concurrent.FutureTask@1e80bfe8 rejected from java.util.concurrent.ThreadPoolExecutor@66a29884[Running, pool size = 100, active threads = 100, queued tasks = 10, completed tasks = 0]
Not finish Yet!
Test92: TestSum is 1000
Test88: TestSum is 1000
Test81: TestSum is 1000
Test100: TestSum is 1000