Java线程池的使用

81 阅读2分钟

我们遇到了什么问题?

我们下载其它平台大量数据耗时过长。

详细如下:

与集采平台对接的时候假设集采品规有50万条数据,每次请求可以返回50条记录,网络耗时大约2s,耗时6小时

50万➗50➗(3600秒➗2)=5.6小时

如果使用多线程,假设100个线程同时执行任务,可能只需4分钟

5.6*60min➗100=3.36分钟

什么是线程?

(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

java中多线程的实现方式

方式一:通过new 手动创建,如Thread thread=new Thread();

方式二:线程池创建,如Executor.execute(Runnable command);(我们采用的方式

/**
     * 用给定的初始参数创建一个新的ThreadPoolExecutor。
     */
    public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量
                              int maximumPoolSize,//线程池的最大线程数
                              long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间
                              TimeUnit unit,//时间单位
                              BlockingQueue<Runnable> workQueue,//任务队列,用来储存等待执行任务的队列
                              ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可
                              RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
                               ) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
------
著作权归所有
原文链接:https://javaguide.cn/java/concurrent/java-thread-pool-summary.html

线程池创建线程流程

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/831a92cd495040f38e0bc70fb43eb8c9~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=811&h=191&s=8514&e=png&b=f4fef7

我们产品中的多线程

创建线程池的方式

ThreadPoolExecutor executor = new ThreadPoolExecutor(0, 100,
        1L, TimeUnit.SECONDS,
        new LinkedBlockingQueue<Runnable>(500));

构造函数参数解析

  1. 核心线程数:0,原因:医院下载品规的频次低。

  2. 最大线程数:100,原因:下载品规的数量大。

  3. 任务队列:500,原因:防止任务队列太长,导致内存溢出。我们的策略是,当业务繁忙时候等等再添加

    //添加任务
            for (int i = 0; i < totalPage; i++) {
                specThreadPoolExecutor.execute(new GetSpecificationsTask(SessionManager.getWsLogisticsId(),SessionManager.getType(),SessionManager.getOpcode(), request,Integer.toString(i),mapVo));
                //当业务繁忙时候等等再添加
                while (specThreadPoolExecutor.getQueue().remainingCapacity() + specThreadPoolExecutor.getMaximumPoolSize() - specThreadPoolExecutor.getPoolSize() == 0) {
                    try {
                        Thread.sleep(100); // 等待一段时间再尝试添加
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
    

使用线程池中一些注意地方

线程并发量是越高越好吗?

不是,创建线程本身也消耗资源,会影响发起请求的应用,也可能击垮被访问的应用。

IO类任务,一般由于IO等待过长,当请求量过大时候,数据操作成为瓶颈,此时线程切换上下文会降低效率。

线程间信息交互

主线程传参通过ThreadLocal处理

public class Task implements Runnable{
​
    RestTemplate restTemplate;
​
    Map<String,Object> params;
​
    String sNumber;
​
    ThreadLocal<String> number=new ThreadLocal<>();
​
    public Task(RestTemplate restTemplate, Map<String, Object> params, String number){
        this.restTemplate=restTemplate;
        this.params=params;
        this.sNumber=number;
    }
    @Override
    public void run() {
        //线程运行时设置值
        number.set(sNumber);
        System.out.println("线程编号:"+number.get()+"为您效劳");
        ResponseEntity<String> result=restTemplate.getForEntity("http://localhost:8083/hello",String.class);
        System.out.println("访问结果:"+result.getBody().toString());
    }
}

主线程通过判断线程池是否有任务需要执行来判断是否所有任务完成

//等待线程池中的任务队列为空,且没有正在执行的线程时,返回操作成功
        while(specThreadPoolExecutor.getQueue().size()!=0||specThreadPoolExecutor.getActiveCount()!=0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

线程池的创建

不应该在用户请求线程中创建,否则可能会创建多个线程池

@Bean
    ThreadPoolExecutor testThreadPoolExecutor(){
        ThreadPoolExecutor executor = new ThreadPoolExecutor(0, 200,
                60L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(500));
        return executor;
    }

线程池的使用

@RequestMapping(value = "/thread", method = RequestMethod.GET)
    public synchronized String hello() {
        StopWatch stopWatch = new StopWatch();
        // 开始时间+
        stopWatch.start();
        //添加任务
        for (int i = 0; i < 5000; i++) {
            testThreadPoolExecutor.execute(new Task(restTemplate, null, Integer.toString(i)));
            while (testThreadPoolExecutor.getQueue().remainingCapacity() + testThreadPoolExecutor.getMaximumPoolSize() - testThreadPoolExecutor.getPoolSize() == 0) {
                try {
                    Thread.sleep(100); // 等待一段时间再尝试添加
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        // 结束时间
        stopWatch.stop();
        // 统计执行时间(秒)
        int time = (int) stopWatch.getTotalTimeMillis();
        //等待线程池中的任务队列为空,且没有正在执行的线程时,返回操作成功
        while(testThreadPoolExecutor.getQueue().size()!=0||testThreadPoolExecutor.getActiveCount()!=0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
​
        return "success";
    }

线程池的关闭

应用销毁的时候

public class MyContextListener implements ServletContextListener {
    private ExecutorService executorService;
​
    public void contextInitialized(ServletContextEvent sce) {
​
    }
​
    public void contextDestroyed(ServletContextEvent sce) {
        // 在Web应用销毁时关闭线程池
        executor.shutdown();
    }
}

引用:

javaguide.cn/java/concur…