一文读懂Java线程池ThreadPoolExecutor的使用

再实际项目中,我们可能会处理执行时间非常短但是数量非常大的请求,这个时候如果为每一个请求创建一个新的线程,可能会导致性能瓶颈。因为线程的创建和销毁的时间可能会大于任务执行的时间,系统性能就会大幅度降低。

JDK1.5提供了线程池的支持,它通过线程的复用技术从而省去线程的频繁创建来达到处理大量并发请求的目的。线程池的原理就是创建了一个运行效率比较优异的“ThreadPool”再池中对线程对象进行创建销毁的管理,使用池时只需要执行具体的任务即可,线程对象的处理再池中被封装了。如下是线程池的一个简单的例子:

public static void main(String[] args) throws IOException {
    ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 3, 30, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
    tpe.execute(() -> {
        System.out.println("我在线程池中" + Thread.currentThread().getName() + "执行");
    });
}

在上面的实例中我们使用ThreadPoolExecutor创建了一个线程池,下面我们首先看下ThreadPoolExecutor的类的层次结构图:

从上面的类的层次结构图我们可知Executor是整个线程池的根接口。其实再这个接口中仅仅一个方法,该方法用于再将来的某个时刻指定给定的命令(线程任务),任务可能在新线程、线程池中存在的线程或正在调用的线程中执行。具体由实现Executor 的类决定。如下为Executor 的定义:

public interface Executor {
    void execute(Runnable command);
}

ExecutorService接口继承了Executor接口,Executor接口仅仅定义了线程池执行任务的方法,而ExecutorService添加更多有关线程池的方法,现在只需要知道有这些方法即可,这些方法会在后面的实现类中进行详细讲解。如下为ExecutorService接口的定义:

public interface ExecutorService extends Executor {
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

直接继承或者实现ExecutorService的有AbstractExecutorService,AbstractExecutorService则是ExecutorService的抽象实现类,是一个抽象类,实现了ExecutorService和Executor的部分功能。它的真正实现则是ThreadPoolExecutor。下面我们就详细介绍线程池ThreadPoolExecutor的使用。

Executor仅仅是一个接口,是一种规范,并没有实现任何功能,我们需要实现类ThreadPoolExecutor来完成指定的功能。
ThreadPoolExecutor提供了几个构造方法创建ThreadPoolExecutor实例,如下是其中一个,其他构造方法最终都会调用这个构造方法。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
}

上面的参数中corePoolSize线程池中保存的线程数,包括空闲线程,也就是核心池的大小;maximumPoolSize 池中允许的最大线程数;keepAliveTime 当线程数量大于corePoolSize时,超过keepAliveTime 时将空闲线程删除;Unit keepAliveTime 的时间单位;workQueue执行前用于保存任务的队列,我们最常用的实LinkedBlockQueue、ArrayBlockQueue和SynchronousQueue。ThreadFactory表示线程工厂,handler表示被拒绝之后的处理。下面我们介绍ThreadPoolExecutor的执行流程。

ThreadPoolExecutor使用不同的队列会有不同的执行流程,我们以LinkedBlockQueue和SynchronousQueue为例,为了简化参数我们使用代码表示:A代表要执行的任务,B代表corePoolSize,C代表maximumPoolSize,E代表 LinkedBlockQueue队列,F代表SynchronousQueue队列,G代表keepLiveTime。

如上流程图当A<=B时,即要执行的任务数量小于等于corePoolSize,线程池会创建线程马上执行任务;不会将任务添加到队列

当A>B&&A<=C时,即执行任务的数量大于corePoolSize但是小于maximumPoolSize时,两种不同的队列会有不同的处理方式,LinkedBlockQueue会将A-B个任务放到LinkedBlockQueue队列,待前面任务执行完之后,从队列取出任务执行,SynchronousQueue则是创建新的线程执行所有的任务。演示代码如下:

public class LinkedQueueThreadPool2 {
    public static void main(String[] args) {
	Runnable runnable = new TpeRunnable("blockQueue");
	ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 5, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
       //要执行的任务数量大于corePoolSize,但小于maximumPoolSize
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	System.out.println(tpe.getPoolSize());//3
	System.out.println(tpe.getActiveCount());//3
	System.out.println(tpe.getQueue().size());//2,两个任务放到等待列表,相当于C和G参数无效。poolSize数量一直小于等于corePoolSize。
	}
}
public class SynchronousQueueThreadPool2 {
    public static void main(String[] args) throws InterruptedException {
	Runnable runnable = new TpeRunnable("SynchQueue");
	ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 5, 5, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
        //要执行的任务数量大于corePoolSize,但小于maximumPoolSize
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
        //迅速创建新线程并执行任务
	System.out.println(tpe.getPoolSize());//5
	System.out.println(tpe.getActiveCount());//5
	System.out.println(tpe.getQueue().size());//0
	Thread.sleep(10000);
	System.out.println(tpe.getPoolSize());//3,5秒之后空闲线程清除,只剩下与corePoolSize数量一样的线程数
    }
}

当A>C时,即执行任务的数量大于maximumPoolSize时,两种不同的队列也会有不同的处理方式,LinkedBlockQueue会将A-B个任务放到LinkedBlockQueue队列,而SynchronousQueue会直接抛出java.util.concurrent.RejectedExecutionException异常。如下为此种情况的代码示例:

public class LinkedQueueThreadPool3 {
    public static void main(String[] args) throws InterruptedException {
	Runnable runnable = new TpeRunnable("blockQueue");
	ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 5, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
       //该处理方式与上面的类似,大于3的任务会加入到队列等待执行
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	System.out.println(tpe.getPoolSize());//3
	System.out.println(tpe.getActiveCount());//3
	System.out.println(tpe.getQueue().size());//3
	Thread.sleep(10000);
	System.out.println(tpe.getPoolSize());//3
    }
}
public class SynchronousQueueThreadPool3 {
    public static void main(String[] args) throws InterruptedException {
	Runnable runnable = new TpeRunnable("SynchQueue");
	ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 5, 5, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	tpe.execute(runnable);
	System.out.println(tpe.getPoolSize());
	System.out.println(tpe.getActiveCount());
        System.out.println(tpe.getQueue().size());
        Thread.sleep(10000);
	System.out.println(tpe.getPoolSize());
        //该示例会抛出异常,当要执行的线程数大于maximumPoolSize时,并且使用SynchronousQueue队列时,会抛出异常
    }
}

为了更清晰的表示上面的执行流程,我画了一个流程图来展示线程池的执行流程,如下图所示:

上面我们演示了ThreadPoolExecutor在不同队列下的使用方式,下面我们介绍ThreadPoolExecutor的关闭的方式,如何正确的关闭我们的线程池。ThreadPoolExecutor提供了shutdown(),shutdownNow()和isShutdown()三种方式关闭线程池。

isShutDown()判断线程池是否已经关闭。只要调用了shutdown()方法就会返回true.

方法shutdown()的作用是使当前未执行完的线程继续执行,而不在执行新的任务。调用shutdown()方法后主线程会马上结束,而线程池内会继续运行,知道所有任务执行完才会停止,如果不调用shutdown()方法线程池会一直保持下去,以便随时执行新的Task任务。Shutdown()方法是非阻塞的。在执行shutdown()方法之后再添加新的任务会抛出RejectedExecutionException异常。

ShutdownNow()方法是中断所有的任务Task,并且抛出InterruptedException异常,如果在线程中使用Thread.currentThread().isInterrupted() == true来判断当前线程的中断状态,而未执行的任务不在执行。如果没有上述判断,则池中正在执行的线程到执行完毕,未执行的线程不在执行,从执行队列中清除。在执行shutdownNow()方法之后再添加新的任务会抛出RejectedExecutionException异常。

前面我们大致介绍了ThreadPoolExecutor,可以看到其构造包含了很多参数,如果使用构造方法创建ThreadPoolExecutor实例,需咬在构造方法中传入多个参数,这样子用起来就会显得十分麻烦,好在官方JDK为我们提供了工具类Executors用于创建线程池。

Executors可以使用newCachedThreadPool()方法创建无边界线程池。并且可以线程自动回收。所谓无边界就是线程池中的线程数量最大值为Integer.MAX_VALUE。我们可以使用newCachedThreadPool()和newCachedThreadPool(ThreadFactory threadFactory)方法创建无边界线程,如下例子使用newCachedThreadPool():

ExecutorService tpe = Executors.newCachedThreadPool();
ExecutorService tpe = Executors.newCachedThreadPool(new MyThreadFactory());
public class MyThreadFactory implements ThreadFactory {
	public Thread newThread(Runnable r) {
		return new Thread(r);
	}
}

Executors可以使用newFixedThreadPool(int nThreads)和newFixedThreadPool(int nThreads, ThreadFactory threadFactory)方法创建有界线程池。线程池的数量为方法中指定的nThreads的值。

ExecutorService es = Executors.newFixedThreadPool(2);

Executors可以使用newSingleThreadPool(int nThreads)和newSingleThreadPool(int nThreads, ThreadFactory threadFactory)方法创建有只有一个线程的线程池。线程池的数量为固定值,只有一个。

ExecutorService es = Executors.newSingleThreadExecutor();

Executors极大的方便了我们创建线程池,其实其内部还是通过ThreadPoolExecutor的构造方法进行初始化了一个线程池实例,如下示例,为创建单个线程的线程池的代码:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,    0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
    }

猜你喜欢

转载自blog.csdn.net/wk19920726/article/details/108616114