深入JAVA并发编程(八):线程池(一)

线程池

为什么使用线程池

多线程的软件设计方法可以最大限度地发挥多核处理器的计算能力,提升系统的性能,但是若是随意使用线程,不加以控制和管理,对系统反而会产生不利的影响,因为线程的创建和销毁需要CPU和内存资源,所以JDK为我们提供了线程池用来管理线程,免去了不必要的开支。

线程池的使用能够为我们带来如下好处

  • 降低资源消耗:如果我们每次执行异步任务都去new一个线程来运行,那么线程的创建和销毁的开销则是难于预估的,而使用线程池就可以直接对线程复用来降低资源消耗。
  • 提高线程的可管理性:使用线程池可以进行统一分配、调优和监控。

JDK已经为我们提供好了一套Executor框架,帮助开发人员来有效地进行线程控制。

首先我们来看一下涉及到的类图,这些类和接口均在JUC包中。

在这里插入图片描述

基础的接口

从Executor开始

Executor是JDK1.5时,随着J.U.C引入的一个接口,引入该接口的主要目的是解耦任务和执行。我们之前通过线程执行一个任务时,往往需要先创建一个线程,然后调用线程的start方法来执行任务,本身既是任务又得做执行操作。

	new Thread(new RunnableTask()).start();

而Executor接口解耦了任务和执行机制,任务为Runnable和Callable等,而执行则交给Executor。该接口只有一个方法,入参为待执行的任务:

public interface Executor {

    //执行给定的任务
    //根据Executor的实现不同, 具体执行方式也不相同.
    void execute(Runnable command);
}

我们可以像下面一样执行任务,而不必关心线程的创建。

Executor executor = someExecutor;       // 创建具体的Executor对象
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());

我们知道JAVA的线程和本地操作系统的线程是一对一对应的,JAVA线程启动时会创建一个操作系统线程,当该JAVA线程终止时,操作系统线程同样会被回收。操作系统会调度所有的线程分配给可用的CPU,由于CPU资源是有限的,线程数量是有上限的,所以一般使用用户的Executor来固定线程的数量和管理,上层通过Executor控制调度,下层由操作系统控制,这其实也是线程池的雏形。

增强的Executor—ExecutorService

ExecutorService继承了Executor,它在Executor的基础上增强了对任务的控制,同时包括对自身生命周期的管理。


public interface ExecutorService extends Executor {

    //关闭执行器
    //(1)已经提交给该执行器的任务将会继续执行, 但是不再接受新任务的提交;
    //(2)如果执行器已经关闭了, 则再次调用没有作用
    //(3)不等待以前提交的任务完成执行
    void shutdown();

    //立即关闭执行器
    //(1)尝试停止所有正在执行的任务,并返回等待执行的任务列表。
    //(2)停止处理已提交但未执行的任务
    //(3)无法保证能够停止成功, 但会尽力尝试(例如, 通过 Thread.interrupt中断任务, 但是不响应中断的任务可能无法终止)
    List<Runnable> shutdownNow();

    //如果该执行器已经关闭, 则返回true.
    boolean isShutdown();

    //如果关闭后所有任务都已完成则返回true
    //除非首先调用 shutdown 或 shutdownNow, 否则该方法永远返回false.
    boolean isTerminated();

    //阻塞调用线程
    //直到所有任务在关闭后完成执行,或发生超时,或当前线程中断等返回true
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;

	//提交一个具有返回值的任务用于执行.
	//通过Future的get方法在成功完成时获取task的返回值.
	//task:提交的任务  T:返回值的类型
    <T> Future<T> submit(Callable<T> task);


    /**
     * 提交一个 Runnable 任务用于执行.
     * 注意: Future的get方法在成功完成时将会返回给定的结果(入参时指定).
     *
     * @param task   待提交的任务
     * @param result 返回的结果
     */
    <T> Future<T> submit(Runnable task, T result);


    /**
     * 提交一个 Runnable 任务用于执行.
     * 注意: Future的get方法在成功完成时将会返回null.
     */
    Future<?> submit(Runnable task);

  	//执行给定集合中的所有任务, 当所有任务都执行完成后, 返回保留任务状态和结果的 Future 列表.
  	//如果等待时发生中断, 会将所有未完成的任务取消.
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

    //执行给定集合中的所有任务, 当所有任务都执行完成后或超时期满时(无论哪个首先发生)
   	//返回保留任务状态和结果的 Future 列表.
    <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;
}

周期任务的调度—ScheduledExecutorService

在一些情况下,我们可能希望提交给执行器的某些任务可以定时执行,所以在ExecutorService的基础上,又提供了一个接口——ScheduledExecutorService,ScheduledExecutorService提供了一系列schedule方法,可以在给定的延迟后执行提交的任务,或者每个指定的周期执行一次提交的任务。

public interface ScheduledExecutorService extends ExecutorService {

    /**
     * 提交一个待执行的任务, 并在给定的延迟后执行该任务.
     *
     * @param command 待执行的任务
     * @param delay   延迟时间
     * @param unit    延迟时间的单位
     */
    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);

    /**
     * 提交一个待执行的任务(具有返回值), 并在给定的延迟后执行该任务.
     *
     * @param command 待执行的任务
     * @param delay   延迟时间
     * @param unit    延迟时间的单位
     * @param <V>     返回值类型
     */
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay, TimeUnit unit);

    /**
     * 创建并执行一个周期性任务。
     * 该任务首先在 initialDelay 后开始执行, 然后在 initialDelay+period 后执行, 接着在 initialDelay + 2 * period 后执行, 依此类推.
     * 如果任务的任何执行遇到异常,则禁止后续执行。
     * 否则,任务将仅通过取消或终止执行者而终止。
     * 如果此任务的任何执行时间超过其周期,则后续执行可能延迟开始,但不会并发执行。
     * 
     * @param command      待执行的任务
     * @param initialDelay 首次执行的延迟时间
     * @param period       连续执行之间的周期
     * @param unit         延迟时间的单位
     */
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);

   	/**
     * 创建并执行一个周期性任务,该任务在给定的初始延迟之后首先启用。
     * 然后在一次执行结束和下一次执行开始之间具有给定的延迟。
     * 如果任务的任何执行遇到异常,则禁止后续执行。
     * 否则,任务将仅通过取消或终止执行者而终止。 
     * 
     *
     * @param command      待执行的任务
     * @param initialDelay 首次执行的延迟时间
     * @param delay        一次执行终止和下一次执行开始之间的延迟
     * @param unit         延迟时间的单位
     */
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);

}

以上就是Executors框架中的三个最核心的接口。

线程池工厂类Executors

Executors

上面三种执行器只是接口,J.U.C提供了许多默认的接口实现。但是大部分情况下不需要我们去创建这些类的实例,因为J.U.C中还提供了一个Executors类,专门用于创建上述接口的实现类对象。Executors其实就是一个简单工厂,它的所有方法都是static的,用户可以根据需要,选择需要创建的线程池实例,Executors一共提供了五类可供创建的Executor执行器实例。

固定线程数的线程池

Executors提供了两种创建具有固定线程数的Executor的方法,固定线程池在初始化时确定其中的线程总数,运行过程中会始终维持线程数量不变。当有新任务提交时,如果线程池中有空闲线程则立即执行,若没有,则任务会被存放在一个任务队列中,待有线程空闲时,便处理任务队列中的任务。

两种创建方法其实都返回了一个ThreadPoolExecutor实例。ThreadPoolExecutor是一个ExecutorService接口的实现类,我们会在后面讲解。

    /**
     * 创建一个固定线程数量的线程池
     * 如果所有线程都处于活动状态时提交了其他任务,则将在队列中等待线程可用。
     * 如果线程在执行过程中由于失败而终止,则在执行后续任务时将使用新线程来代替它。
     * 
     * nThreads:创建的线程数量
     */
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

    /**
     * 创建一个具有固定线程数的线程池
     * 在需要时使用提供的 ThreadFactory 创建新线程.
     * 如果所有线程都处于活动状态时提交了其他任务,则将在队列中等待线程可用。
     * 如果线程在执行过程中由于失败而终止,则在执行后续任务时将使用新线程来代替它。
     *
     * @param nThreads 线程数量
     * @param threadFactory 创建线程池的工厂
     */
    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }

第二个方法在创建线程池时需要传递一个创建线程的工厂类,为什么这样做呢?因为线程池的创建肯定需要创建线程,一般情况下我们需要通过new Thread()这种方式去创建线程,但是我们可能希望给线程设置一些属性,例如名称、守护线程、线程组等等。线程池中的线程非常多,如果每一个都手动配置的话就非常繁琐,而ThreadFactory作为线程工厂就可以很好的帮我们解决这个问题,我们只需要指定ThreadFactory实例,就可以决定线程的具体创建方式。

Executors提供了静态内部类,实现了ThreadFactory接口,最简单且常用的就是下面这个DefaultThreadFactory

    static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

可以看到,DefaultThreadFactory 初始化的时候定义了线程组、线程名称等信息,每创建一个线程,都给线程统一分配这些信息,避免了一个个手工通过new的方式创建线程,又可进行工厂的复用。

我们来看一个简单的使用案例。

public class FixThreadPoolDemo {

    static class MyTask implements Runnable{

        @Override
        public void run() {
            System.out.println(System.currentTimeMillis()+":线程id:"+Thread.currentThread().getId());
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        MyTask task=new MyTask();
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i=0;i<10;i++){
            executorService.submit(task);
        }

    }
}

我们创建了一个只有5个线程的线程池,然后提交了10个任务,所以会分两批次执行,前后相差10000毫秒。

在这里插入图片描述

单个线程的线程池

Executors还提供了两种创建只有单个线程的线程池的方法。该方法返回一个只有一个线程的线程池,若有多余的任务提交则会被放入任务队列,待线程空闲,按照先进先出的规则依次执行。

    /**
    *创建一个使用单个worker线程的Executor
    */
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

    /**
	 * 创建一个使用单个worker线程的Executor
	 * 在需要时使用提供的 ThreadFactory 创建新线程.
	 */
    public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }

可以看到,只有单个线程的线程池其实就是指定线程数为1的固定线程池,主要区别就是,返回的Executor实例用了一个FinalizableDelegatedExecutorService对象进行包装。

我们来看下FinalizableDelegatedExecutorService,该类 只定义了一个finalize方法:

    static class FinalizableDelegatedExecutorService
        extends DelegatedExecutorService {
        FinalizableDelegatedExecutorService(ExecutorService executor) {
            super(executor);
        }
        protected void finalize() {
            super.shutdown();
        }
    }

核心是其继承的DelegatedExecutorService ,这是一个包装类,实现了ExecutorService的所有方法,但是内部实现其实都委托给了传入的ExecutorService 实例:

    static class DelegatedExecutorService extends AbstractExecutorService {
        private final ExecutorService e;
        DelegatedExecutorService(ExecutorService executor) { e = executor; }
        public void execute(Runnable command) { e.execute(command); }
        public void shutdown() { e.shutdown(); }
        public List<Runnable> shutdownNow() { return e.shutdownNow(); }
        public boolean isShutdown() { return e.isShutdown(); }
        public boolean isTerminated() { return e.isTerminated(); }
        public boolean awaitTermination(long timeout, TimeUnit unit)
            throws InterruptedException {
            return e.awaitTermination(timeout, unit);
        }
        public Future<?> submit(Runnable task) {
            return e.submit(task);
        }
        public <T> Future<T> submit(Callable<T> task) {
            return e.submit(task);
        }
        public <T> Future<T> submit(Runnable task, T result) {
            return e.submit(task, result);
        }
        public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
            throws InterruptedException {
            return e.invokeAll(tasks);
        }
        public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                             long timeout, TimeUnit unit)
            throws InterruptedException {
            return e.invokeAll(tasks, timeout, unit);
        }
        public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
            throws InterruptedException, ExecutionException {
            return e.invokeAny(tasks);
        }
        public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                               long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException {
            return e.invokeAny(tasks, timeout, unit);
        }
    }

为什么要多此一举,加上这样一个委托层?因为返回的ThreadPoolExecutor包含一些设置线程池大小的方法——比如setCorePoolSize,对于只有单个线程的线程池来说,我们是不希望用户通过强转的方式使用这些方法的,所以需要一个包装类,只暴露ExecutorService本身的方法。

可缓存的线程池

有些情况下,我们虽然创建了具有一定线程数的线程池,但出于资源利用率的考虑,可能希望在特定的时候对线程进行回收(比如线程超过指定时间没有被使用),Executors就提供了这种类型的线程池。可以创建一个根据实际情况调整线程数量的线程池。线程池的线程数量不固定,若所有线程都在工作,又有新的任务提交,则会创建新的线程。

    /**
     * 创建一个线程池,该线程池根据需要创建新线程
     * 如果线程池中没有现有线程可用,则将创建新线程并将其添加到池中。
     * 默认情况下60秒未使用的线程将被终止并从缓存中移除。
     */
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

    /**
     * 创建一个线程池,该线程池根据需要创建新线程
     * 如果线程池中没有现有线程可用,则将创建新线程并将其添加到池中。
     * 默认情况下60秒未使用的线程将被终止并从缓存中移除。
     * 在需要时使用提供的 ThreadFactory 创建新线程.
     */
    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

可以看到,返回的还是ThreadPoolExecutor对象,只是指定了超时时间,另外线程池中线程的数量在[0, Integer.MAX_VALUE]之间。

可延时/周期调度的线程池

如果有任务需要延迟/周期调用,就需要返回ScheduledExecutorService接口的实例,ScheduledThreadPoolExecutor就是实现了ScheduledExecutorService接口的一种Executor,和ThreadPoolExecutor一样,这个我们后面会专门讲解。

    /**
     * 创建一个具有固定线程数的可调度的线程池.
     * 该线程池可以计划在给定的延迟,或周期性地执行。
     */
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

    /**
     * 创建一个具有固定线程数的可调度的线程池.
     * 该线程池可以计划在给定的延迟,或周期性地执行。
     * 在需要时使用提供的 ThreadFactory 创建新线程.
     */
    public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }

我们来看一个简单的使用案例。

public class SchedulePoolDemo {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(System.currentTimeMillis());
            }
        },0,2, TimeUnit.SECONDS);
    }
}

我们创建了一个周期性的线程池,每两秒执行一次任务。

在这里插入图片描述

如果我们的任务执行时间超过2s,那么它的任务会重叠嘛?我们可以测试一下:

public class SchedulePoolDemo {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis());
            }
        },0,2, TimeUnit.SECONDS);
    }
}

在这里插入图片描述

我们发现任务是不会重叠的,ScheduledExecutorService 会让任务结束之后立马执行下一次任务,所以周期就变成了5秒。

基础的知识已经了解完毕,下一章我们会详细讲解ThreadPoolExecutor和ScheduledThreadPoolExecutor。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值