Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去,欢迎建议和指导):https://github.com/Snailclimb/Java_Guide

历史优质文章推荐:

Java并发编程指南专栏

分布式系统的经典基础理论

可能是最漂亮的Spring事务管理详解

面试中关于Java虚拟机(jvm)的问题看这篇就够了

目录:

目录

本节思维导图:

思维导图源文件+思维导图软件关注微信公众号:“Java面试通关手册” 回复关键字:“Java多线程” 免费领取。

一 使用线程池的好处

线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。

这里借用《Java并发编程的艺术》提到的来说一下使用线程池的好处

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

二 Executor 框架

2.1 简介

Executor 框架是Java5之后引进的,在Java 5之后,通过 Executor 来启动线程比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免 this 逃逸问题。

补充:this逃逸是指在构造函数返回之前其他线程就持有该对象的引用. 调用尚未构造完全的对象的方法可能引发令人疑惑的错误。

2.2 Executor 框架结构(主要由三大部分组成)

1 任务。

执行任务需要实现的Runnable接口Callable接口

Runnable接口Callable接口实现类都可以被ThreadPoolExecutorScheduledThreadPoolExecutor执行。

两者的区别:

Runnable接口不会返回结果但是Callable接口可以返回结果。后面介绍Executors类的一些方法的时候会介绍到两者的相互转换。

2 任务的执行

如下图所示,包括任务执行机制的核心接口Executor ,以及继承自Executor 接口的ExecutorService接口ScheduledThreadPoolExecutorThreadPoolExecutor这两个关键类实现了ExecutorService接口

注意: 通过查看ScheduledThreadPoolExecutor源代码我们发现ScheduledThreadPoolExecutor实际上是继承了ThreadPoolExecutor并实现了ScheduledExecutorService ,而ScheduledExecutorService又实现了ExecutorService,正如我们下面给出的类关系图显示的一样。

ThreadPoolExecutor类描述:

  1. //AbstractExecutorService实现了ExecutorService接口
  2. public class ThreadPoolExecutor extends AbstractExecutorService

ScheduledThreadPoolExecutor类描述:

  1. //ScheduledExecutorService实现了ExecutorService接口
  2. public class ScheduledThreadPoolExecutor
  3. extends ThreadPoolExecutor
  4. implements ScheduledExecutorService

3 异步计算的结果

Future接口以及Future接口的实现类FutureTask类

当我们把Runnable接口Callable接口的实现类提交(调用submit方法)给ThreadPoolExecutorScheduledThreadPoolExecutor时,会返回一个FutureTask对象

我们以AbstractExecutorService接口中的一个submit方法为例子来看看源代码:

  1. public Future<?> submit(Runnable task) {
  2. if (task == null) throw new NullPointerException();
  3. RunnableFuture<Void> ftask = newTaskFor(task, null);
  4. execute(ftask);
  5. return ftask;
  6. }

上面方法调用的newTaskFor方法返回了一个FutureTask对象。

  1. protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
  2. return new FutureTask<T>(runnable, value);
  3. }

2.3 Executor 框架的使用示意图

  1. 主线程首先要创建实现Runnable或者Callable接口的任务对象。

    备注: 工具类Executors可以实现Runnable对象和Callable对象之间的相互转换。(Executors.callable(Runnable task)或Executors.callable(Runnable task,Object resule))。

  2. 然后可以把创建完成的Runnable对象直接交给ExecutorService执行(ExecutorService.execute(Runnable command));或者也可以把Runnable对象或Callable对象提交给ExecutorService执行(ExecutorService.submit(Runnable task)或ExecutorService.submit(Callable task))。

执行execute()方法和submit()方法的区别是什么呢?

1)execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;

2)submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

  1. 如果执行ExecutorService.submit(…),ExecutorService将返回一个实现Future接口的对象(我们刚刚也提到过了执行execute()方法和submit()方法的区别,到目前为止的JDK中,返回的是FutureTask对象)。由于FutureTask实现了Runnable,程序员也可以创建FutureTask,然后直接交给ExecutorService执行。

  2. 最后,主线程可以执行FutureTask.get()方法来等待任务执行完成。主线程也可以执行FutureTask.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。

三 ThreadPoolExecutor详解

线程池实现类ThreadPoolExecutor是Executor 框架最核心的类,先来看一下这个类中比较重要的四个属性

3.1 ThreadPoolExecutor类的四个比较重要的属性

3.2 ThreadPoolExecutor类中提供的四个构造方法

我们看最长的那个,其余三个都是在这个构造方法的基础上产生(给定某些默认参数的构造方法)

  1. /**
  2. * 用给定的初始参数创建一个新的ThreadPoolExecutor。
  3. * @param keepAliveTime 当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,
  4. *核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime;
  5. * @param unit keepAliveTime参数的时间单位
  6. * @param workQueue 等待队列,当任务提交时,如果线程池中的线程数量大于等于corePoolSize的时候,把该任务封装成一个Worker对象放入等待队列;
  7. *
  8. * @param threadFactory 执行者创建新线程时使用的工厂
  9. * @param handler RejectedExecutionHandler类型的变量,表示线程池的饱和策略。
  10. * 如果阻塞队列满了并且没有空闲的线程,这时如果继续提交任务,就需要采取一种策略处理该任务。
  11. * 线程池提供了4种策略:
  12. 1.AbortPolicy:直接抛出异常,这是默认策略;
  13. 2.CallerRunsPolicy:用调用者所在的线程来执行任务;
  14. 3.DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
  15. 4.DiscardPolicy:直接丢弃任务;
  16. */
  17. public ThreadPoolExecutor(int corePoolSize,
  18. int maximumPoolSize,
  19. long keepAliveTime,
  20. TimeUnit unit,
  21. BlockingQueue<Runnable> workQueue,
  22. ThreadFactory threadFactory,
  23. RejectedExecutionHandler handler) {
  24. if (corePoolSize < 0 ||
  25. maximumPoolSize <= 0 ||
  26. maximumPoolSize < corePoolSize ||
  27. keepAliveTime < 0)
  28. throw new IllegalArgumentException();
  29. if (workQueue == null || threadFactory == null || handler == null)
  30. throw new NullPointerException();
  31. this.corePoolSize = corePoolSize;
  32. this.maximumPoolSize = maximumPoolSize;
  33. this.workQueue = workQueue;
  34. this.keepAliveTime = unit.toNanos(keepAliveTime);
  35. this.threadFactory = threadFactory;
  36. this.handler = handler;
  37. }

3.3 如何创建ThreadPoolExecutor

方式一:通过构造方法实现(官方API文档并不推荐,所以建议使用第二种方式)



方式二:通过Executor 框架的工具类Executors来实现

我们可以创建三种类型的ThreadPoolExecutor:

  • FixedThreadPool
  • SingleThreadExecutor
  • CachedThreadPool

对应Executors工具类中的方法如图所示:

3.4 FixedThreadPool详解

FixedThreadPool被称为可重用固定线程数的线程池。通过Executors类中的相关源代码来看一下相关实现:

  1. /**
  2. * 创建一个可重用固定数量线程的线程池
  3. *在任何时候至多有n个线程处于活动状态
  4. *如果在所有线程处于活动状态时提交其他任务,则它们将在队列中等待,
  5. *直到线程可用。 如果任何线程在关闭之前的执行期间由于失败而终止,
  6. *如果需要执行后续任务,则一个新的线程将取代它。池中的线程将一直存在
  7. *知道调用shutdown方法
  8. * @param nThreads 线程池中的线程数
  9. * @param threadFactory 创建新线程时使用的factory
  10. * @return 新创建的线程池
  11. * @throws NullPointerException 如果threadFactory为null
  12. * @throws IllegalArgumentException if {@code nThreads <= 0}
  13. */
  14. public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
  15. return new ThreadPoolExecutor(nThreads, nThreads,
  16. 0L, TimeUnit.MILLISECONDS,
  17. new LinkedBlockingQueue<Runnable>(),
  18. threadFactory);
  19. }

另外还有一个FixedThreadPool的实现方法,和上面的类似,所以这里不多做阐述:

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

从上面源代码可以看出新创建的FixedThreadPool的corePoolSize和maximumPoolSize都被设置为nThreads。

FixedThreadPool的execute()方法运行示意图(该图片来源:《Java并发编程的艺术》):

上图说明:

  1. 如果当前运行的线程数小于corePoolSize,则创建新的线程来执行任务;
  2. 当前运行的线程数等于corePoolSize后,将任务加入LinkedBlockingQueue;
  3. 线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue中获取任务来执行;

FixedThreadPool使用无界队列 LinkedBlockingQueue(队列的容量为Intger.MAX_VALUE)作为线程池的工作队列会对线程池带来如下影响:

  1. 当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize;
  2. 由于1,使用无界队列时maximumPoolSize将是一个无效参数;
  3. 由于1和2,使用无界队列时keepAliveTime将是一个无效参数;
  4. 运行中的FixedThreadPool(未执行shutdown()或shutdownNow()方法)不会拒绝任务

3.5 SingleThreadExecutor详解

SingleThreadExecutor是使用单个worker线程的Executor。下面看看SingleThreadExecutor的实现:

  1. /**
  2. *创建使用单个worker线程运行无界队列的Executor
  3. *并使用提供的ThreadFactory在需要时创建新线程
  4. *
  5. * @param threadFactory 创建新线程时使用的factory
  6. *
  7. * @return 新创建的单线程Executor
  8. * @throws NullPointerException 如果ThreadFactory为空
  9. */
  10. public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
  11. return new FinalizableDelegatedExecutorService
  12. (new ThreadPoolExecutor(1, 1,
  13. 0L, TimeUnit.MILLISECONDS,
  14. new LinkedBlockingQueue<Runnable>(),
  15. threadFactory));
  16. }
  1. public static ExecutorService newSingleThreadExecutor() {
  2. return new FinalizableDelegatedExecutorService
  3. (new ThreadPoolExecutor(1, 1,
  4. 0L, TimeUnit.MILLISECONDS,
  5. new LinkedBlockingQueue<Runnable>()));
  6. }

从上面源代码可以看出新创建的SingleThreadExecutor的corePoolSize和maximumPoolSize都被设置为1.其他参数和FixedThreadPool相同。SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为Intger.MAX_VALUE)。SingleThreadExecutor使用无界队列作为线程池的工作队列会对线程池带来的影响与FixedThreadPool相同。

SingleThreadExecutor的运行示意图(该图片来源:《Java并发编程的艺术》):

上图说明;

  1. 如果当前运行的线程数少于corePoolSize,则创建一个新的线程执行任务;
  2. 当前线程池中有一个运行的线程后,将任务加入LinkedBlockingQueue
  3. 线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue中获取任务来执行;

3.6 CachedThreadPool详解

CachedThreadPool是一个会根据需要创建新线程的线程池。下面通过源码来看看 CachedThreadPool的实现:

  1. /**
  2. * 创建一个线程池,根据需要创建新线程,但会在先前构建的线程可用时重用它,
  3. *并在需要时使用提供的ThreadFactory创建新线程。
  4. * @param threadFactory 创建新线程使用的factory
  5. * @return 新创建的线程池
  6. * @throws NullPointerException 如果threadFactory为空
  7. */
  8. public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
  9. return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
  10. 60L, TimeUnit.SECONDS,
  11. new SynchronousQueue<Runnable>(),
  12. threadFactory);
  13. }
  1. public static ExecutorService newCachedThreadPool() {
  2. return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
  3. 60L, TimeUnit.SECONDS,
  4. new SynchronousQueue<Runnable>());
  5. }

CachedThreadPool的corePoolSize被设置为空(0),maximumPoolSize被设置为Integer.MAX.VALUE,即它是无界的,这也就意味着如果主线程提交任务的速度高于maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建新的线程。极端情况下,这样会导致耗尽cpu和内存资源。

CachedThreadPool的execute()方法的执行示意图(该图片来源:《Java并发编程的艺术》):

上图说明:

  1. 首先执行SynchronousQueue.offer(Runnable task)。如果当前maximumPool中有闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行,execute()方法执行完成,否则执行下面的步骤2;
  2. 当初始maximumPool为空,或者maximumPool中没有空闲线程时,将没有线程执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这种情况下,步骤1将失败,此时CachedThreadPool会创建新线程执行任务,execute方法执行完成;

3.7 ThreadPoolExecutor使用示例

3.7.1 示例代码

首先创建一个Runnable接口的实现类(当然也可以是Callable接口,我们上面也说了两者的区别是:Runnable接口不会返回结果但是Callable接口可以返回结果。后面介绍Executors类的一些方法的时候会介绍到两者的相互转换。)

  1. import java.util.Date;
  2. /**
  3. * 这是一个简单的Runnable类,需要大约5秒钟来执行其任务。
  4. */
  5. public class WorkerThread implements Runnable {
  6. private String command;
  7. public WorkerThread(String s) {
  8. this.command = s;
  9. }
  10. @Override
  11. public void run() {
  12. System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date());
  13. processCommand();
  14. System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date());
  15. }
  16. private void processCommand() {
  17. try {
  18. Thread.sleep(5000);
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. @Override
  24. public String toString() {
  25. return this.command;
  26. }
  27. }

编写测试程序,我们这里以FixedThreadPool为例子

  1. import java.util.concurrent.ExecutorService;
  2. import java.util.concurrent.Executors;
  3. public class ThreadPoolExecutorDemo {
  4. public static void main(String[] args) {
  5. //创建一个FixedThreadPool对象
  6. ExecutorService executor = Executors.newFixedThreadPool(5);
  7. for (int i = 0; i < 10; i++) {
  8. //创建WorkerThread对象(WorkerThread类实现了Runnable 接口)
  9. Runnable worker = new WorkerThread("" + i);
  10. //执行Runnable
  11. executor.execute(worker);
  12. }
  13. //终止线程池
  14. executor.shutdown();
  15. while (!executor.isTerminated()) {
  16. }
  17. System.out.println("Finished all threads");
  18. }
  19. }

输出示例:

  1. pool-1-thread-5 Start. Time = Thu May 31 10:22:52 CST 2018
  2. pool-1-thread-3 Start. Time = Thu May 31 10:22:52 CST 2018
  3. pool-1-thread-2 Start. Time = Thu May 31 10:22:52 CST 2018
  4. pool-1-thread-4 Start. Time = Thu May 31 10:22:52 CST 2018
  5. pool-1-thread-1 Start. Time = Thu May 31 10:22:52 CST 2018
  6. pool-1-thread-4 End. Time = Thu May 31 10:22:57 CST 2018
  7. pool-1-thread-1 End. Time = Thu May 31 10:22:57 CST 2018
  8. pool-1-thread-2 End. Time = Thu May 31 10:22:57 CST 2018
  9. pool-1-thread-5 End. Time = Thu May 31 10:22:57 CST 2018
  10. pool-1-thread-3 End. Time = Thu May 31 10:22:57 CST 2018
  11. pool-1-thread-5 Start. Time = Thu May 31 10:22:57 CST 2018
  12. pool-1-thread-2 Start. Time = Thu May 31 10:22:57 CST 2018
  13. pool-1-thread-1 Start. Time = Thu May 31 10:22:57 CST 2018
  14. pool-1-thread-4 Start. Time = Thu May 31 10:22:57 CST 2018
  15. pool-1-thread-3 Start. Time = Thu May 31 10:22:57 CST 2018
  16. pool-1-thread-5 End. Time = Thu May 31 10:23:02 CST 2018
  17. pool-1-thread-1 End. Time = Thu May 31 10:23:02 CST 2018
  18. pool-1-thread-2 End. Time = Thu May 31 10:23:02 CST 2018
  19. pool-1-thread-3 End. Time = Thu May 31 10:23:02 CST 2018
  20. pool-1-thread-4 End. Time = Thu May 31 10:23:02 CST 2018
  21. Finished all threads

3.7.2 shutdown()VS shutdownNow()

shutdown()方法表明关闭已在Executor上调用,因此不会再向DelayedPool添加任何其他任务(由ScheduledThreadPoolExecutor类在内部使用)。 但是,已经在队列中提交的任务将被允许完成。

另一方面,shutdownNow()方法试图终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的List。

3.7.3 isTerminated() Vs isShutdown()

isShutdown()表示执行程序正在关闭,但并非所有任务都已完成执行。

另一方面,isShutdown()表示所有线程都已完成执行。

四 ScheduledThreadPoolExecutor详解

4.1 简介

ScheduledThreadPoolExecutor主要用来在给定的延迟后运行任务,或者定期执行任务。

ScheduledThreadPoolExecutor使用的任务队列DelayQueue封装了一个PriorityQueue,PriorityQueue会对队列中的任务进行排序,执行所需时间短的放在前面先被执行(ScheduledFutureTask的time变量小的先执行),如果执行所需时间相同则先提交的任务将被先执行(ScheduledFutureTask的squenceNumber变量小的先执行)。

ScheduledThreadPoolExecutor和Timer的比较:

  • Timer对系统时钟的变化敏感,ScheduledThreadPoolExecutor不是;
  • Timer只有一个执行线程,因此长时间运行的任务可以延迟其他任务。 ScheduledThreadPoolExecutor可以配置任意数量的线程。 此外,如果你想(通过提供ThreadFactory),你可以完全控制创建的线程;
  • 在TimerTask中抛出的运行时异常会杀死一个线程,从而导致Timer死机:-( ...即计划任务将不再运行。ScheduledThreadExecutor不仅捕获运行时异常,还允许您在需要时处理它们(通过重写afterExecute方法 ThreadPoolExecutor)。抛出异常的任务将被取消,但其他任务将继续运行。

综上,在JDK1.5之后,你没有理由再使用Timer进行任务调度了。

备注: Quartz是一个由java编写的任务调度库,由OpenSymphony组织开源出来。在实际项目开发中使用Quartz的还是居多,比较推荐使用Quartz。因为Quartz理论上能够同时对上万个任务进行调度,拥有丰富的功能特性,包括任务调度、任务持久化、可集群化、插件等等。

4.2 ScheduledThreadPoolExecutor运行机制

ScheduledThreadPoolExecutor的执行主要分为两大部分:

  1. 当调用ScheduledThreadPoolExecutor的 scheduleAtFixedRate() 方法或者scheduleWirhFixedDelay() 方法时,会向ScheduledThreadPoolExecutor的 DelayQueue 添加一个实现了 RunnableScheduledFutur 接口的 ScheduledFutureTask
  2. 线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务。

ScheduledThreadPoolExecutor为了实现周期性的执行任务,对ThreadPoolExecutor做了如下修改:

  • 使用 DelayQueue 作为任务队列;
  • 获取任务的方不同
  • 执行周期任务后,增加了额外的处理

4.3 ScheduledThreadPoolExecutor执行周期任务的步骤

  1. 线程1从DelayQueue中获取已到期的ScheduledFutureTask(DelayQueue.take())。到期任务是指ScheduledFutureTask的time大于等于当前系统的时间;
  2. 线程1执行这个ScheduledFutureTask;
  3. 线程1修改ScheduledFutureTask的time变量为下次将要被执行的时间;
  4. 线程1把这个修改time之后的ScheduledFutureTask放回DelayQueue中(DelayQueue.add())。

4.4 ScheduledThreadPoolExecutor使用示例

  1. 创建一个简单的实现Runnable接口的类(我们上面的例子已经实现过)

  2. 测试程序使用ScheduledExecutorService和ScheduledThreadPoolExecutor实现的java调度。

  1. /**
  2. * 使用ScheduledExecutorService和ScheduledThreadPoolExecutor实现的java调度程序示例程序。
  3. */
  4. public class ScheduledThreadPoolDemo {
  5. public static void main(String[] args) throws InterruptedException {
  6. //创建一个ScheduledThreadPoolExecutor对象
  7. ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
  8. //计划在某段时间后运行
  9. System.out.println("Current Time = "+new Date());
  10. for(int i=0; i<3; i++){
  11. Thread.sleep(1000);
  12. WorkerThread worker = new WorkerThread("do heavy processing");
  13. //创建并执行在给定延迟后启用的单次操作。
  14. scheduledThreadPool.schedule(worker, 10, TimeUnit.SECONDS);
  15. }
  16. //添加一些延迟让调度程序产生一些线程
  17. Thread.sleep(30000);
  18. System.out.println("Current Time = "+new Date());
  19. //关闭线程池
  20. scheduledThreadPool.shutdown();
  21. while(!scheduledThreadPool.isTerminated()){
  22. //等待所有任务完成
  23. }
  24. System.out.println("Finished all threads");
  25. }
  26. }

运行结果:

  1. Current Time = Wed May 30 17:11:16 CST 2018
  2. pool-1-thread-1 Start. Time = Wed May 30 17:11:27 CST 2018
  3. pool-1-thread-2 Start. Time = Wed May 30 17:11:28 CST 2018
  4. pool-1-thread-3 Start. Time = Wed May 30 17:11:29 CST 2018
  5. pool-1-thread-1 End. Time = Wed May 30 17:11:32 CST 2018
  6. pool-1-thread-2 End. Time = Wed May 30 17:11:33 CST 2018
  7. pool-1-thread-3 End. Time = Wed May 30 17:11:34 CST 2018
  8. Current Time = Wed May 30 17:11:49 CST 2018
  9. Finished all threads

4.4.1 ScheduledExecutorService scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)方法

我们可以使用ScheduledExecutorService scheduleAtFixedRate方法来安排任务在初始延迟后运行,然后在给定的时间段内运行。

时间段是从池中第一个线程的开始,因此如果您将period指定为1秒并且线程运行5秒,那么只要第一个工作线程完成执行,下一个线程就会开始执行。

  1. for (int i = 0; i < 3; i++) {
  2. Thread.sleep(1000);
  3. WorkerThread worker = new WorkerThread("do heavy processing");
  4. // schedule task to execute at fixed rate
  5. scheduledThreadPool.scheduleAtFixedRate(worker, 0, 10,
  6. TimeUnit.SECONDS);
  7. }

输出示例:

  1. Current Time = Wed May 30 17:47:09 CST 2018
  2. pool-1-thread-1 Start. Time = Wed May 30 17:47:10 CST 2018
  3. pool-1-thread-2 Start. Time = Wed May 30 17:47:11 CST 2018
  4. pool-1-thread-3 Start. Time = Wed May 30 17:47:12 CST 2018
  5. pool-1-thread-1 End. Time = Wed May 30 17:47:15 CST 2018
  6. pool-1-thread-2 End. Time = Wed May 30 17:47:16 CST 2018
  7. pool-1-thread-3 End. Time = Wed May 30 17:47:17 CST 2018
  8. pool-1-thread-1 Start. Time = Wed May 30 17:47:20 CST 2018
  9. pool-1-thread-4 Start. Time = Wed May 30 17:47:21 CST 2018
  10. pool-1-thread-2 Start. Time = Wed May 30 17:47:22 CST 2018
  11. pool-1-thread-1 End. Time = Wed May 30 17:47:25 CST 2018
  12. pool-1-thread-4 End. Time = Wed May 30 17:47:26 CST 2018
  13. pool-1-thread-2 End. Time = Wed May 30 17:47:27 CST 2018
  14. pool-1-thread-1 Start. Time = Wed May 30 17:47:30 CST 2018
  15. pool-1-thread-3 Start. Time = Wed May 30 17:47:31 CST 2018
  16. pool-1-thread-5 Start. Time = Wed May 30 17:47:32 CST 2018
  17. pool-1-thread-1 End. Time = Wed May 30 17:47:35 CST 2018
  18. pool-1-thread-3 End. Time = Wed May 30 17:47:36 CST 2018
  19. pool-1-thread-5 End. Time = Wed May 30 17:47:37 CST 2018
  20. pool-1-thread-1 Start. Time = Wed May 30 17:47:40 CST 2018
  21. pool-1-thread-2 Start. Time = Wed May 30 17:47:41 CST 2018
  22. Current Time = Wed May 30 17:47:42 CST 2018
  23. pool-1-thread-1 End. Time = Wed May 30 17:47:45 CST 2018
  24. pool-1-thread-2 End. Time = Wed May 30 17:47:46 CST 2018
  25. Finished all threads
  26. Process finished with exit code 0

4.4.2 ScheduledExecutorService scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit)方法

ScheduledExecutorService scheduleWithFixedDelay方法可用于以初始延迟启动周期性执行,然后以给定延迟执行。 延迟时间是线程完成执行的时间。

  1. for (int i = 0; i < 3; i++) {
  2. Thread.sleep(1000);
  3. WorkerThread worker = new WorkerThread("do heavy processing");
  4. scheduledThreadPool.scheduleWithFixedDelay(worker, 0, 1,
  5. TimeUnit.SECONDS);
  6. }

输出示例:

  1. Current Time = Wed May 30 17:58:09 CST 2018
  2. pool-1-thread-1 Start. Time = Wed May 30 17:58:10 CST 2018
  3. pool-1-thread-2 Start. Time = Wed May 30 17:58:11 CST 2018
  4. pool-1-thread-3 Start. Time = Wed May 30 17:58:12 CST 2018
  5. pool-1-thread-1 End. Time = Wed May 30 17:58:15 CST 2018
  6. pool-1-thread-2 End. Time = Wed May 30 17:58:16 CST 2018
  7. pool-1-thread-1 Start. Time = Wed May 30 17:58:16 CST 2018
  8. pool-1-thread-3 End. Time = Wed May 30 17:58:17 CST 2018
  9. pool-1-thread-4 Start. Time = Wed May 30 17:58:17 CST 2018
  10. pool-1-thread-2 Start. Time = Wed May 30 17:58:18 CST 2018
  11. pool-1-thread-1 End. Time = Wed May 30 17:58:21 CST 2018
  12. pool-1-thread-1 Start. Time = Wed May 30 17:58:22 CST 2018
  13. pool-1-thread-4 End. Time = Wed May 30 17:58:22 CST 2018
  14. pool-1-thread-2 End. Time = Wed May 30 17:58:23 CST 2018
  15. pool-1-thread-2 Start. Time = Wed May 30 17:58:23 CST 2018
  16. pool-1-thread-4 Start. Time = Wed May 30 17:58:24 CST 2018
  17. pool-1-thread-1 End. Time = Wed May 30 17:58:27 CST 2018
  18. pool-1-thread-2 End. Time = Wed May 30 17:58:28 CST 2018
  19. pool-1-thread-1 Start. Time = Wed May 30 17:58:28 CST 2018
  20. pool-1-thread-2 Start. Time = Wed May 30 17:58:29 CST 2018
  21. pool-1-thread-4 End. Time = Wed May 30 17:58:29 CST 2018
  22. pool-1-thread-4 Start. Time = Wed May 30 17:58:30 CST 2018
  23. pool-1-thread-1 End. Time = Wed May 30 17:58:33 CST 2018
  24. pool-1-thread-2 End. Time = Wed May 30 17:58:34 CST 2018
  25. pool-1-thread-1 Start. Time = Wed May 30 17:58:34 CST 2018
  26. pool-1-thread-2 Start. Time = Wed May 30 17:58:35 CST 2018
  27. pool-1-thread-4 End. Time = Wed May 30 17:58:35 CST 2018
  28. pool-1-thread-4 Start. Time = Wed May 30 17:58:36 CST 2018
  29. pool-1-thread-1 End. Time = Wed May 30 17:58:39 CST 2018
  30. pool-1-thread-2 End. Time = Wed May 30 17:58:40 CST 2018
  31. pool-1-thread-5 Start. Time = Wed May 30 17:58:40 CST 2018
  32. pool-1-thread-4 End. Time = Wed May 30 17:58:41 CST 2018
  33. pool-1-thread-2 Start. Time = Wed May 30 17:58:41 CST 2018
  34. Current Time = Wed May 30 17:58:42 CST 2018
  35. pool-1-thread-5 End. Time = Wed May 30 17:58:45 CST 2018
  36. pool-1-thread-2 End. Time = Wed May 30 17:58:46 CST 2018
  37. Finished all threads

4.4.3 scheduleWithFixedDelay() vs scheduleAtFixedRate()

scheduleAtFixedRate(...)将延迟视为两个任务开始之间的差异(即定期调用)

scheduleWithFixedDelay(...)将延迟视为一个任务结束与下一个任务开始之间的差异

scheduleAtFixedRate(): 创建并执行在给定的初始延迟之后,随后以给定的时间段首先启用的周期性动作; 那就是执行将在initialDelay之后开始,然后initialDelay+period ,然后是initialDelay + 2 * period ,等等。 如果任务的执行遇到异常,则后续的执行被抑制。 否则,任务将仅通过取消或终止执行人终止。 如果任务执行时间比其周期长,则后续执行可能会迟到,但不会同时执行。

scheduleWithFixedDelay() : 创建并执行在给定的初始延迟之后首先启用的定期动作,随后在一个执行的终止和下一个执行的开始之间给定的延迟。 如果任务的执行遇到异常,则后续的执行被抑制。 否则,任务将仅通过取消或终止执行终止。

五 各种线程池的适用场景介绍

FixedThreadPool: 适用于为了满足资源管理需求,而需要限制当前线程数量的应用场景。它适用于负载比较重的服务器;

SingleThreadExecutor: 适用于需要保证顺序地执行各个任务并且在任意时间点,不会有多个线程是活动的应用场景。

CachedThreadPool: 适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器;

ScheduledThreadPoolExecutor: 适用于需要多个后台执行周期任务,同时为了满足资源管理需求而需要限制后台线程的数量的应用场景,

SingleThreadScheduledExecutor: 适用于需要单个后台线程执行周期任务,同时保证顺序地执行各个任务的应用场景。

六 总结

本节只是简单的介绍了一下使用线程池的好处,然后花了大量篇幅介绍Executor 框架。详细介绍了Executor 框架中ThreadPoolExecutor和ScheduledThreadPoolExecutor,并且通过实例详细讲解了ScheduledThreadPoolExecutor的使用。对于FutureTask 只是粗略带过,因为篇幅问题,并没有深究它的原理,后面的文章会进行补充。这一篇文章只是大概带大家过一下线程池的基本概览,深入讲解的地方不是很多,后续会通过源码深入研究其中比较重要的一些知识点。

最后,就是这两周要考试了,会抽点时间出来简单应付一下学校考试了。然后,就是写这篇多线程的文章废了好多好多时间。一直不知从何写起。

参考

《Java并发编程的艺术》

Java Scheduler ScheduledExecutorService ScheduledThreadPoolExecutor Example

java.util.concurrent.ScheduledThreadPoolExecutor Example

ThreadPoolExecutor – Java Thread Pool Example

我是Snailclimb,一个以架构师为5年之内目标的小小白。 欢迎关注我的微信公众号:"Java面试通关手册"(一个有温度的微信公众号,期待与你共同进步~~~坚持原创,分享美文,分享各种Java学习资源)

最后,就是使用阿里云服务器一段时间后,感觉阿里云真的很不错,就申请做了阿里云大使,然后这是我的优惠券地址.

Java多线程学习(八)线程池与Executor 框架的更多相关文章

  1. Java多线程学习之线程池源码详解

    0.使用线程池的必要性 在生产环境中,如果为每个任务分配一个线程,会造成许多问题: 线程生命周期的开销非常高.线程的创建和销毁都要付出代价.比如,线程的创建需要时间,延迟处理请求.如果请求的到达率非常 ...

  2. JAVA多线程学习七-线程池

    为什么用线程池 1.创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率 例如: 记创建线程消耗时间T1,执行任务消耗时间T2,销毁线程消耗时间T3 如果T1+T3> ...

  3. Java多线程系列--“JUC线程池”03之 线程池原理(二)

    概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...

  4. Java多线程系列--“JUC线程池”06之 Callable和Future

    概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...

  5. Java多线程系列--“JUC线程池”02之 线程池原理(一)

    概要 在上一章"Java多线程系列--“JUC线程池”01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析Th ...

  6. Java多线程系列--“JUC线程池”04之 线程池原理(三)

    转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509960.html 本章介绍线程池的生命周期.在"Java多线程系列--“基础篇”01之 基 ...

  7. Java多线程系列--“JUC线程池”05之 线程池原理(四)

    概要 本章介绍线程池的拒绝策略.内容包括:拒绝策略介绍拒绝策略对比和示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3512947.html 拒绝策略 ...

  8. 转:java多线程CountDownLatch及线程池ThreadPoolExecutor/ExecutorService使用示例

    java多线程CountDownLatch及线程池ThreadPoolExecutor/ExecutorService使用示例 1.CountDownLatch:一个同步工具类,它允许一个或多个线程一 ...

  9. 线程池之Executor框架

    线程池之Executor框架 Java的线程既是工作单元,也是执行机制.从JDK5开始,把工作机单元和执行机制分离开来.工作单元包括Runnable和Callable,而执行机制由Executor框架 ...

  10. 【多线程】Android多线程学习笔记——线程池

    Java线程池采用了享元设计模式,在系统中维持一定数量的线程,用于处理异步或并发需求,在平时处理异步或并发任务时被广泛使用.这里基于JDK1.8和Android28来整理一些关于线程池的知识点. 一. ...

随机推荐

  1. 用IDEA开发简单的Servlet

    最近学习java,主要是servlet相关的内容.IDEA和servlet之前都没有碰过,所以做了一下小实验,走了一些弯路:这里把一个完整的步骤写出来,加深一下印象. IDEA创建项目步骤 1. 在i ...

  2. Thymeleaf 学习笔记

    (一)Thymeleaf 是个什么? 简单说, Thymeleaf 是一个跟 Velocity.FreeMarker 类似的模板引擎,它可以完全替代 JSP .相较与其他的模板引擎,它有如下三个极吸引 ...

  3. 四则运算安卓版ver.mk2

    做了一点微小的变动....非常微小...不过美观点了就是...那就是----加背景! 变更后的部分代码: <LinearLayout xmlns:android="http://sch ...

  4. Beta阶段综合报告

    一. 敏捷冲刺每日报告 http://www.cnblogs.com/mia0502/p/7767628.html http://www.cnblogs.com/mia0502/p/7773872.h ...

  5. 我是一个程序猿 ——《不是书评 :<我是一只IT小小鸟>》有感

    读了刘未鹏先生的文章<不是书评 :<我是一只IT小小鸟>>,产生了诸多共鸣,更明白了不少道理. 首先是一个很平常的现象,进度条效应,在操作移动终端上的软件时,如果没有进度条,人 ...

  6. github优缺点

    以前bitbucket沒有支援git github可以直接在網站上瀏覽push的圖片 github 可以針對code行數直接留言與回覆 github Markdown支援很好 github 的issu ...

  7. Django 图片上传、存储与显示

    参考博客:http://www.cognize.me/2016/05/09/djangopic 开始之前要先安装python图像处理库:pip install --use-wheel Pillow 一 ...

  8. 洛谷P13445 [USACO5.4]奶牛的电信Telecowmunication(网络流)

    题目描述 农夫约翰的奶牛们喜欢通过电邮保持联系,于是她们建立了一个奶牛电脑网络,以便互相交流.这些机器用如下的方式发送电邮:如果存在一个由c台电脑组成的序列a1,a2,...,a(c),且a1与a2相 ...

  9. C# DataTable Select用法

    DataRow[] dr = ds.Tables[0].Select("列名='该列你要查询的值'"); DataRow[] dr = ds.Tables[0].Select(&q ...

  10. Chemical table CodeForces - 1012B

    题意: 一个棋盘 对于任何一个棋盘中的矩形 如果 任意三角存在棋子 则第四个角会自动生成一个棋子  求铺满整个棋盘 我们至少要向棋盘里加多少枚棋子 解析: 这题就是求图中有多少个连通图,可以直接dfs ...