异步线程池优化记录

转自: 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

猜你喜欢

转载自blog.csdn.net/he_qiao_2010/article/details/80796840