前言
在上一篇文章中【线程池使用不当的危害(一):局部变量线程池、类变量线程池的使用方法】我们讨论了应该如何使用线程池的问题,得到了不能随意在代码中new 局部变量的线程池,相信你已经知道了基本的使用规则。那么本篇文章就是带你使用工具去图像化的,直观化的体验web程序在相对的并发下,如果不复用线程池,无限制的创建局部的线程池,会带来的问题。
ok,话不多说,我们开始。首先介绍下我的思路,我打算:
1、使用jconsole工具形象化的观察程序的线程创建情况
2、分别写两个测试方法,一个是局部变量线程池,另一个则是类变量定义线程池。并且写一个方法去模拟http请求,不断调用写好的测试方法
3、观察jconsole的线程创建情况
下面是最终的线程创建的图像:(一看就知道,复用线程池的方法不会创建大量线程)
已经理解到意思的朋友可以直接跳过代码,看最后的结论,测试代码并不重要
测试代码
类变量线程池
/**
* @author: 代码丰
* @Date: 2023
*/
@Slf4j
@RestController
public class StaticThreadPoolController {
@Autowired
GenerateService circleGenerateService;
//计算最终的时间
private static long totalTime = 0L;
//计算最终调用次数
private static long totalLoopNumber = 0L;
//创建线程池 核心最大 都设置为了10
private static final ThreadPoolExecutor executorService = new ThreadPoolExecutor(10, 10, 10, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1),new ThreadPoolExecutor.AbortPolicy());
static {
executorService.setThreadFactory(new NameCustomizedThreadFactory("测试"));
}
@RequestMapping("/static")
public String test() throws Exception{
long begin = System.currentTimeMillis();
executorService.allowCoreThreadTimeOut(true);
ArrayList<Future<Boolean>> futures = Lists.newArrayList();
//每一个界面请求都会调用1000次的打印方法
for (int i = 1; i <= 1000; i++) {
Future<Boolean> submit = executorService.submit(() -> {
circleGenerateService.circleGenerateCurrentThread();
return true;
});
futures.add(submit);
}
//计算最后成功的笔数
long count = futures.stream().map(s -> {
try {
return s.get();
} catch (Exception e) {
return false;
}
}).filter(s -> s).count();
log.info("执行完成,共【{}】笔", count);
totalLoopNumber++;
//为了区分每一笔的过程,停顿下
Thread.sleep(100);
long end = System.currentTimeMillis();
totalTime += (end-begin);
return "总次数:"+count+"循环调用次数:"+totalLoopNumber+"用时:"+ totalTime +"毫秒";
}
}
局部变量线程池
/**
* @author: 代码丰
* @Date: 2023
*/
@Slf4j
@RestController
public class LocalThreadPoolController {
@Autowired
GenerateService circleGenerateService;
//计算最终的时间
private static long totalTime = 0L;
//计算最终调用次数
private static long totalLoopNumber = 0L;
@RequestMapping ("/nonStatic")
public String test() throws Exception{
long begin = System.currentTimeMillis();
//创建线程池 核心最大 都设置为了10
ThreadPoolExecutor executorService = new ThreadPoolExecutor(10, 10, 5, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),new ThreadPoolExecutor.AbortPolicy());
//允许核心消亡
executorService.allowCoreThreadTimeOut(true);
ArrayList<Future<Boolean>> futures = Lists.newArrayList();
//每一个界面请求都会调用1000次的打印方法
for (int i = 1; i <= 1000; i++) {
Future<Boolean> submit = executorService.submit(() -> {
circleGenerateService.circleGenerateCurrentThread();
return true;
});
futures.add(submit);
}
//计算最后成功的笔数
long count = futures.stream().map(s -> {
try {
return s.get();
} catch (Exception e) {
return false;
}
}).filter(s -> s).count();
log.info("执行完成,共【{}】笔", count);
totalLoopNumber++;
executorService.shutdown();
//为了区分每一笔的过程,停顿下
Thread.sleep(100);
long end = System.currentTimeMillis();
totalTime += (end-begin);
return "总次数:"+count+"循环调用次数:"+totalLoopNumber+"用时:"+ totalTime +"毫秒";
}
}
模拟http请求
/**
* @author: 代码丰
* @Date: 2023
*/
public class HttpUtils {
public static void testPost() throws IOException {
// URL url = new URL("http://localhost:8090/nonStatic");
URL url = new URL("http://localhost:8090/static");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(), "8859_1");
out.write("username=kevin&password=*********");
out.flush();
out.close();
String sCurrentLine;
String sTotalString;
sCurrentLine = "";
sTotalString = "";
InputStream l_urlStream;
l_urlStream = connection.getInputStream();
BufferedReader l_reader = new BufferedReader(new InputStreamReader(
l_urlStream));
while ((sCurrentLine = l_reader.readLine()) != null) {
sTotalString += sCurrentLine + "/r/n";
}
System.out.println(sTotalString);
}
public static void main(String[] args) throws IOException {
for ( int a = 0; a<10; a++){
testPost();
}
}
}
每一个线程要做的打印的事情
/**
* @author: 代码丰
* @Date: 2023
*/
@Component
public class GenerateService {
private static int number = 1;
public String circleGenerateCurrentThread(){
System.out.println(Thread.currentThread().getName() + "打印 "+ number+++"次");
try{
Thread.sleep(100);
}catch (Exception e){
}
return "打印次数"+number;
}
}
结论:
线程池的变化:
使用static复用的线程池,不会大量创建线程
使用局部变量new出来的线程池,会大量创建线程
内存的变化
发散探究:
还有其他问题,是我想要发散探究的。
1、jsonsole的基本使用
解答:JConsole 是一个内置 Java 性能分析器。你只需要找到jdk安装目录下的bin目录,双击执行即可
2、处理每一次从浏览器发起请求的处理线程是怎么被创建的?
解答:我们千万不要忘记Web程序通常也是跑在tomcat这个容器里面的,tomcat本身就是支持多线程的,即Tomcat使用了线程池,在用户发起的一个访问web资源的请求过来时,如果线程池里面有空闲的线程,那么会在线程池里面取一个工作线程来处理该请求,一旦工作线程当前在处理请求,其他请求就不会被分配到该工作线程上,直到该请求处理完成。
你可以理解为每一次的请求就是对应的一个线程去处理。
3、线程池核心线程数是否越大越好呢?哪种场景下,核心线程可以设置大一点?
这块楼主不是很懂实际怎么做,具体的工作中没有针对此处做过性能优化,仅有理论基础,有知道的小伙伴请评论区指教。(这里给了一个理论链接 点击跳转)
最终结论:
由此可以得出线程池的使用的核心思想,就是复用,线程池都不复用了,那创建这个池子有什么用?和每一个请求都新开一个线程去处理没有区别了。