ThreadPoolExecutor的基本使用方法

先来看一个最基本的多线程创建和启动:

class SomeThead extends Thraad   {
   
public void run()   {
       
//do something here
   
}
}


public static void main(String[] args){
    SomeThread oneThread =
new SomeThread();
   
oneThread.start();
}

实现Runnable的方法大同小异,就不写了。

这样会带来几个问题:

  1. 使用线程的时候就去创建一个线程如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率。
  2. 线程能共享系统资源,如果同时执行的线程过多,就有可能导致系统资源不足而产生阻塞的情况。
  3. 不能对线程进行简单的管理等。

 

针对这些问题, Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。

 

那么,我们应该如何创建一个线程池呢?

Java中已经提供了创建线程池的一个类:Executor

 

而我们创建时,一般使用它的子类:ThreadPoolExecutor.

 

对线程池的配置,就是对ThreadPoolExecutor构造函数的参数的配置,先来看所提供的四个构造函数

 

五个参数的构造器:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)

 

六个参数:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory)

六个参数:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler)

 

七个参数:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

 

首先不要被这么多个参数给吓到了,实际上也就七种类型,请听慢慢道来:

 

一,int corePoolSize  =>  该线程池中核心线程数最大值

 

       核心线程:当有新的线程创建的时候,如果当前线程总数没达到这个值,那新建的就是核心线程,否则就是非核心线程。

 

二,int maximumPoolSize  =>  该线程池中线程总数最大值

 

       线程总数 = 核心线程数 + 非核心线程数,就是这么好理解。

 

三,long keepAliveTime  =>  该线程池中非核心线程闲置超时时长

 

       非核心线程起作用,如果闲置状态的时长超过这个参数所设定的时长,就会被销毁掉

 

四,TimeUnit unit  =>  keepAliveTime的单位

 

TimeUnit是一个枚举类型:里面包括:

NANOSECONDS 1微毫秒 = 1微秒 / 1000

MICROSECONDS 1微秒 = 1毫秒 / 1000

MILLISECONDS 1毫秒 = 1 /1000

SECONDS

MINUTES

HOURS 小时

DAYS

 

再用一张图来表示这几个参数的关系:

https://ss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=1414237573,4093572289&fm=173&app=25&f=JPEG?w=617&h=442&s=0572E5331B07734B4DE994C50300E0A1

线程池(有颜色的区域)中的corePoolSize就是线程池中的核心线程数量,这几个核心线程,只是在没有用的时候,也不会被回收,maximumPoolSize就是线程池中可以容纳的最大线程的数量。

 

到这里,前四个参数应该已经相当清楚了吧,继续。

 

五,BlockingQueue<Runnable> workQueue

         =>  该线程池中的任务队列(阻塞队列):维护着等待执行的Runnable对象
 

       当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务。用得最多的有以下三种:

  • ArrayBlockingQueue

可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误

  • LinkedBlockingQueue

这个队列的总线程数被corePoolSize所限制,如果有新任务进来,核心线程已满,则需要等待,导致maximumPoolSize在这个队列中不起作用。

  • SynchronousQueue

这个队列接收到任务的时候,会直接提交给线程处理,如果所有线程都忙,那就新建线程来处理,此时maximumPoolSize应指定为Integer.MAX_VALUE为无限大。

 

六,ThreadFactory threadFactory

       =>  创建线程的方式,这是一个接口

 

public interface ThreadFactory {
    Thread newThread(Runnable r);
}

new 它的时候需要实现newThread方法,一般用不上。

 

七,RejectedExecutionHandler handler

       =>  就是抛出异常专用的,比如上面提到的两个错误发生了,就会由这个handler抛出异常,不指定也有个默认的,所以,完全可以不填。

 

至此,我们可以创建出一个能用的线程池啦:

核心线程:10

线程池大小:1000

等待存活时间:0,单位毫秒

阻塞队列:ArrayBlockingQueue

ArrayBlockingQueue<Runnable> threadQueue = new ArrayBlockingQueue<>(1000);
ThreadPoolExecutor executor
    = new ThreadPoolExecutor(10,1000,0, TimeUnit.MILLISECONDS, threadQueue);

 

 

说完了参数,又创建了一个线程池,我们看看咋个用:

 

首先,在ThreadPoolExecutor类中有几个非常重要的方法:

       execute()

       submit()

       shutdown()

       shutdownNow()

 

一,向线程池提交任务:execute()

        

通过ThreadPoolExecutor.execute(Runnable command)方法即可向线程池内添加一个任务。

你可以这样写:

executor.execute(new Runnable() {
    @Override
    public void run() {
        // do sth.
    }
});

 

也可以把任务单独拆出来写成一个Task类,去实现Runnable方法。(推荐这样写)

二,向线程池提交任务2submit()

 

submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。这里暂不讨论,以后有机会再说。

 

三,关闭线程池:shutdown()shutdownNow()

 

       是在ExecutorService中声明的方法。

shutdown调用后,不可以再submit新的task,已经submit的将继续执行。

shutdownNow试图停止当前正执行的task,并返回尚未执行的tasklist

换句话说,shutdown只是起到通知的作用,通知不再接受新的任务。而后者则会中断所有线程。所以,关闭线程可以这么写:

try {
    // 通知需要停止
    executor.shutdown();
    // 给一个线程最后的执行时间
    if (executor.awaitTermination(10,TimeUnit.MILLISECONDS)){
        // 中断所有线程
        executor.shutdownNow();
    }
} catch (InterruptedException e) {
    e.printStackTrace();
}

 

至此,你已经可以用ThreadPoolExecutor去创建一个线程池,往里提交任务,然后再关闭了!

END.

猜你喜欢

转载自blog.csdn.net/alexsjr/article/details/82391150