线程池使用不当的危害(二):使用jconsole图像化展示局部、类变量线程池的创建

前言

  在上一篇文章中【线程池使用不当的危害(一):局部变量线程池、类变量线程池的使用方法】我们讨论了应该如何使用线程池的问题,得到了不能随意在代码中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、线程池核心线程数是否越大越好呢?哪种场景下,核心线程可以设置大一点?

   这块楼主不是很懂实际怎么做,具体的工作中没有针对此处做过性能优化,仅有理论基础,有知道的小伙伴请评论区指教。(这里给了一个理论链接 点击跳转)

最终结论:

   由此可以得出线程池的使用的核心思想,就是复用,线程池都不复用了,那创建这个池子有什么用?和每一个请求都新开一个线程去处理没有区别了。

猜你喜欢

转载自blog.csdn.net/qq_44716086/article/details/129266042