最近项目一个项目要结项了,但客户要求 TPS 能达到上千,而用我写的代码再怎么弄成只能达到 30 + 的 TPS,然后我又将代码中能缓存的都缓存了,能拆分的也都拆分了,拆分时用的线程池来实现的;其实现的代码主要为以前写的一篇博客中的实现方式来实现的。如下:
多线程之 futureTask(future,callable) 实例, jdbc 数据多线程查询https://blog.csdn.net/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 int finalI = i;
Callable callable = new Callable() {
@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());
}
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());
}
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 int finalI = i;
Callable callable = new Callable() {
@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 的最大线程数有关,具体身体关系还得靠后面的慢慢探索了。(贼尴尬,这么基础的代码问题居然给犯下了这么大一个错误. 还好及时地修改了。哈哈)
作者:水中加点糖
来源链接:
https://blog.csdn.net/puhaiyang/article/details/80530495