那些天,我用错的JAVA线程池用法

本文通过对比newFixedThreadPool与newCachedThreadPool的使用,分析了如何优化线程池以提高系统的吞吐量(TPS)。通过具体的代码示例和JMeter测试,展示了正确的线程池使用方式能够显著提升系统性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近项目一个项目要结项了,但客户要求TPS能达到上千,而用我写的代码再怎么弄成只能达到30+的TPS,然后我又将代码中能缓存的都缓存了,能拆分的也都拆分了,拆分时用的线程池来实现的;其实现的代码主要为以前写的一篇博客中的实现方式来实现的。如下:

多线程之futureTask(future,callable)实例,jdbc数据多线程查询(https://blog.youkuaiyun.com/puhaiyang/article/details/78041046)

在其中用到了线程池,为了方便,线程池是采用如下代码new出来的

final ExecutorService executorService = Executors.newFixedThreadPool(10);  

通过自己仔细想想后,感觉这代码总有哪里写得不对,为此特意写了下DEMO代码来,并用jmeter自己跑一下自己测下:

 @RequestMapping(value = "doTest")
    public Object doTest(@RequestParam(defaultValue = "false") Boolean shutdown,
                         @RequestParam(defaultValue = "10") Integer threadCount,
                         @RequestParam(defaultValue = "100") Integer sleepTime,
                         @RequestParam(defaultValue = "10") Integer queryCount) {
        long beginTime = System.currentTimeMillis();
        final ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
        for (int i = 0; i < queryCount; i++) {
            int finalI = i;
            Callable<Integer> callable = new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    Thread.sleep(sleepTime);
                    logger.debug("index:{} threadInfo:{}", finalI, Thread.currentThread().toString());
                    return finalI;
                }
            };
            FutureTask futureTask = new FutureTask(callable);
            executorService.submit(futureTask);
        }
        if (shutdown) {
            executorService.shutdown();
        }

        Long endTime = System.currentTimeMillis();
        endTime = endTime - beginTime;
        logger.info("info:{}", endTime);
        return atomicInteger.addAndGet(endTime.intValue()) + "   this:" + endTime;
    } 

代码如上所示,然后我用jmeter对此进行了测试,测试1000个请求去访问,每个任务线程休眠时间设的为1秒,TPS为20多。

一想这确实挺低的,然后分析其原因,想着是不是springBoot的线程数给的太少了,于是乎又把tomcat的最大线程数进行了修改,由默认的200修改为了500,但发现没啥大的变化,想了想后,可能问题不是tomcat的配置导致的。

server:
  tomcat:
    max-threads: 500 

然后又通过Java VisualVM工具看了看线程信息,没发现啥问题。

然后出去静了静,听了一两首音乐后想着起来Executors还有一个newCachedThreadPool()的用法,它与newFixedThreadPool()的区别通过源码可以大概知道个一二:

newFixedThreadPool:

    /**
     * Creates a thread pool that reuses a fixed number of threads
     * operating off a shared unbounded queue.  At any point, at most
     * {@code nThreads} threads will be active processing tasks.
     * If additional tasks are submitted when all threads are active,
     * they will wait in the queue until a thread is available.
     * If any thread terminates due to a failure during execution
     * prior to shutdown, a new one will take its place if needed to
     * execute subsequent tasks.  The threads in the pool will exist
     * until it is explicitly {@link ExecutorService#shutdown shutdown}.
     *
     * @param nThreads the number of threads in the pool
     * @return the newly created thread pool
     * @throws IllegalArgumentException if {@code nThreads <= 0}
     */
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

newCachedThreadPool:

    /**
     * Creates a thread pool that creates new threads as needed, but
     * will reuse previously constructed threads when they are
     * available.  These pools will typically improve the performance
     * of programs that execute many short-lived asynchronous tasks.
     * Calls to {@code execute} will reuse previously constructed
     * threads if available. If no existing thread is available, a new
     * thread will be created and added to the pool. Threads that have
     * not been used for sixty seconds are terminated and removed from
     * the cache. Thus, a pool that remains idle for long enough will
     * not consume any resources. Note that pools with similar
     * properties but different details (for example, timeout parameters)
     * may be created using {@link ThreadPoolExecutor} constructors.
     *
     * @return the newly created thread pool
     */
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

newFixedThreadPool是创建一个大小固定的线程池,线程数固定,也不会被回收

newCachedThreadPool是创建一个大小为MAX_VALUE的线程数,并具有缓存功能,如果60秒内线程一直 处于空闲,则会进行回收

另外,线程池的shutdown方法doc文档的解释如下:

Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted. Invocation has no additional effect if already shut down.
This method does not wait for previously submitted tasks to complete execution. Use awaitTermination to do that.

指的是等待线程池执行shutdown方法后就不再接收新的执行目标了,等当前线程池中的现场执行完毕后,此线程池就会关闭掉了。

通过查看JAVA源码中的注释信息后才得知原来我之前写的代码有了一个大大的BUG,不应该执行完一次后就立即把线程池给shutdown掉,这样的话,线程池的意义就没多大的意思了,跟new Thread的就差不多了。尴尬了!

然后乎将测试代码修改为如下的代码:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicInteger;

@RestController
@RequestMapping(value = "test")
public class TestController {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private AtomicInteger atomicInteger = new AtomicInteger(0);
    final ExecutorService executorService = Executors.newCachedThreadPool();

    @RequestMapping(value = "doTest")
    public Object doTest(@RequestParam(defaultValue = "false") Boolean shutdown,
                         @RequestParam(defaultValue = "10") Integer threadCount,
                         @RequestParam(defaultValue = "100") Integer sleepTime,
                         @RequestParam(defaultValue = "10") Integer queryCount) {
        long beginTime = System.currentTimeMillis();
//        final ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
        for (int i = 0; i < queryCount; i++) {
            int finalI = i;
            Callable<Integer> callable = new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    Thread.sleep(sleepTime);
                    logger.debug("index:{} threadInfo:{}", finalI, Thread.currentThread().toString());
                    return finalI;
                }
            };
            FutureTask futureTask = new FutureTask(callable);
            executorService.submit(futureTask);
        }
        if (shutdown) {
            executorService.shutdown();
        }

        Long endTime = System.currentTimeMillis();
        endTime = endTime - beginTime;
        logger.info("info:{}", endTime);
        return atomicInteger.addAndGet(endTime.intValue()) + "   this:" + endTime;
    }

}

调用时,shutdown传入false,并且线程池的new方法放到上面的公共方法区域中,而不应该是来一个请求就new一个线程池出来。然后将同样的请求用jmeter测试后,发现能达到300多了,比之前的20多提升了许多倍!

总结:

通过上面的测试,发现了一个我之前用错的JAVA线程池的用法,通过jmeter工具测试后才知道其性能是如何的大。

同时在通过修改springboot的配置信息后,发现springBoot能创建的线程池最大线程数也与其tomcat的最大线程数有关,具体身体关系还得靠后面的慢慢探索了。(贼尴尬,这么基础的代码问题居然给犯下了这么大一个错误.还好及时地修改了。哈哈)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水中加点糖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值