java多线程:使用newFixedThreadPool方法创建指定线程数量的线程池

写在前面的话:本文给出了如何创建一个有界线程池的一种方法,并对其中的问题进行了分析理解,其中最后一个分析问题个人觉得非常有价值,通过这个问题能帮我们更好的理解线程池。

1.创建无界线程池可能会造成的问题

在上一篇博客中我们对线程池有一个简单的了解,并知道了如何创建线程池以及让线程池中的线程执行。但是上一篇博客中所用的newCachedThreadPool()方法创建线程并不好,因为如果使用不当会造成内存溢出异常。参见博客:https://blog.csdn.net/wenniuwuren/article/details/51700080

2.如何创建指定线程数量的线程池?

代码如下:

package com.springboot.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestMyFixedThreadPool {
    public static void main(String[] args) {
        /*创建指定数量的线程池*/
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        executorService.execute(new MyRunnable("小明"));
        executorService.execute(new MyRunnable("小华"));
        executorService.execute(new MyRunnable("李雷"));
        executorService.execute(new MyRunnable("韩梅梅"));
        executorService.execute(new MyRunnable("小王"));
    }
}


class MyRunnable implements Runnable{
   private String userName;

   MyRunnable(String userName){
       this.userName=userName;
   }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName()+" userName="+userName+" begin "+System.currentTimeMillis());
            Thread.sleep(300l);
            System.out.println(Thread.currentThread().getName()+" userName="+userName+" end "+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

pool-1-thread-2 userName=小华 begin 1572234540656
pool-1-thread-1 userName=小明 begin 1572234540656
pool-1-thread-3 userName=李雷 begin 1572234540681
pool-1-thread-2 userName=小华 end 1572234540956
pool-1-thread-1 userName=小明 end 1572234540957
pool-1-thread-1 userName=韩梅梅 begin 1572234540958
pool-1-thread-2 userName=小王 begin 1572234540958
pool-1-thread-3 userName=李雷 end 1572234540981
pool-1-thread-2 userName=小王 end 1572234541258
pool-1-thread-1 userName=韩梅梅 end 1572234541258

从运行结果可以看出,由于我们一开始的时候指定线程池大小是3,但是加入了5个任务,所以开始时候只能有3个任务在运行,当线程池中的线程有空闲时立马接受新的任务并开始执行,从运行结果上面我们也可以看出他们在时间是连续的:即上一个任务的结束时间即为下一个任务的开始时间。

3.引入问题分析:当线程池中的线程把任务执行完以后它们还在继续运行吗?

如果我们没有试验或者没有深入了解线程池的话,我们可能会这样回答,都执行完成任务了还执行个啥啊?为了节省资源肯定是不运行。(语气肯定,信心满满)但是实际上却是这样的:

如图,我们可以看到此时任务全部执行完了,但是程序依然在运行,说明仍然有线程在运行。说明我们的臆想出来的答案是错误的。所以新的思考随之 而来:到底是什么线程在运行呢?我的猜想是这样:当我线程开始执行以后会随之创建一个新的守护线程,除非认为关闭否则他将一直在运行,从而导致程序一直在运行,至于线程池中的线程我也认为是任务结束以后将会死亡。但是只有猜想没有验证是肯定行不通的,上一个猜想才让我被打脸。

验证猜想:使用java虚拟机提供的管理工具来进行分析,打开管理工具(打开命令行窗口输入:jvisualvm命令)

 红色标注的地方为当前正在运行的程序,点开以后我们可以看到此程序所包含的线程。

我们惊奇的发现,我创建线程池的时候指定的三个线程它们依然还在,状态为“驻留”状态。那为什么是这样的呢?

看源码:

  public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

此方法的第一个和第三个参数分别为:corePoolSize、keepAliveTime

继续进入ThreadPoolExecutor类:

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

看到这两参数的注释:

corePoolSize:the number of threads to keep in the pool, even if they are idle.【除非设置了{@code allowCoreThreadTimeOut},即使它们处于空闲状态也要保留在池中的线程数】

keepAliveTime:when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.【当线程数大于内核数时,这是多余的空闲线程将在终止之前等待新任务的最长时间。】

我们可以反过来想:如果线程的数量没有超过内核数,就算是空闲线程他也将一直处于运行状态。

 

此时又有一个新问题,是不是当我们创建一个指定数量的线程池的时候,就已经有指定数量的在线程池中运行呢?

通过代码来验证:

package com.springboot.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestMyFixedThreadPool {
    public static void main(String[] args) {
        /*创建指定数量的线程池*/
        ExecutorService executorService = Executors.newFixedThreadPool(3);
      
    }
}


class MyRunnable implements Runnable{
   private String userName;

   MyRunnable(String userName){
       this.userName=userName;
   }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName()+" userName="+userName+" begin "+System.currentTimeMillis());
            Thread.sleep(300l);
            System.out.println(Thread.currentThread().getName()+" userName="+userName+" end "+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

再看java虚拟机:

之前的程序不在了。

执行一个任务:

package com.springboot.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestMyFixedThreadPool {
    public static void main(String[] args) {
        /*创建指定数量的线程池*/
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        executorService.execute(new MyRunnable("小明"));
       /* executorService.execute(new MyRunnable("小华"));
        executorService.execute(new MyRunnable("李雷"));
        executorService.execute(new MyRunnable("韩梅梅"));
        executorService.execute(new MyRunnable("小王"));*/
    }
}


class MyRunnable implements Runnable{
   private String userName;

   MyRunnable(String userName){
       this.userName=userName;
   }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName()+" userName="+userName+" begin "+System.currentTimeMillis());
            Thread.sleep(300l);
            System.out.println(Thread.currentThread().getName()+" userName="+userName+" end "+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

任务执行完,程序依然在运行。

我们可以看到,此时有一个线程在运行,导致程序在运行。 

至此我们可以得出结论:

       当我们创建一个指定数量的线程池的时候,会初始化一个线程指定大小线程池,但是并没有线程在运行,只有当我们调用executorService.execute();方法以后才会有线程加入线程池,并且当他执行完任务以后(处于空闲状态)它不会被移除线程池(前提:线程数没有超过核心线程池大小,如果线程数超过内核数的话他将会在超过keepAliveTime以后消失),而是一直处于驻留状态,同时实际线程的数量遵行这样的原则:如果线程数小于线程池的大小以实际线程数量为准,最大只能和线程池大小一样。【注:本文精华】

 

参考文献:《java并发编程核心框架与方法》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章