Java并发(基础知识)—— 创建、运行以及停止一个线程中讲解了两种创建线程的方式:直接继承Thread类以及实现Runnable接口并赋给Thread,这两种创建线程的方式在线程比较少的时候是没有问题的,但是当需要创建大量线程时就会出现问题,因为这种使用方法把线程创建语句随意地散落在代码中,无法统一管理线程,我们将无法管理创建线程的数量,而过量的线程创建将直接使系统崩溃。

从高内聚角度讲,我们应该创建一个统一的创建以及运行接口,为我们管理这些线程,这个统一的创建与运行接口就是JDK 5的Executor框架。

Executor框架

在Java类库中,任务执行的主要抽象不是Thread,而是Executor,该接口定义如下:

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

  虽然Executor是一个简单的接口,但它却为灵活且强大的异步任务执行框架提供了基础,该框架能够支持多种不同类型的任务执行策略,它提供了一种标准的方法将任务的提交过程与执行过程解耦开来。

Executor基于生产者-消费者模式,提交任务的操作相当于生产者(生成待完成的工作单元),执行任务的线程则相当于消费者(执行工作单元)。如果要在一个程序中实现一个生产者-消费者模式,那么最简单的方式就是使用Executor。

Executor接口定义了提交任务的方法,但却没有定义关闭的方法,ExecutorService接口扩展了Executor接口,添加了一些用于生命周期管理的方法:

public interface ExecutorService extends Executor {
void shutdown(); List<Runnable> shutdownNow(); boolean isShutdown(); boolean isTerminated(); boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException; ...
}

  ExecutorService的生命周期有3种状态:运行、关闭和已终止。ExecutorService在初始创建时处于运行状态。shutdown方法将执行平缓的关闭过程:不再接受新的任务,同时等待已经提交的任务执行完成——包括那些还未开始执行的任务。shutdownNow方法将执行粗暴的关闭过程:它将尝试取消所有执行中的任务,并且不再启动队列中尚未开始执行的任务。

在所有任务都完成后,ExecutorService将转入终止状态。可以调用awaitTermination来等待ExecutorService到达终止状态,或者通过调用isTerminated来轮询ExecutorService是否已经终止。通常在调用awaitTermination之后会立即调用shutdown,从而产生同步地关闭ExecutorService的效果。

线程池

Executor框架的核心是线程池。线程池是指管理一组同构工作线程的资源池,在"线程池中执行任务"比"为每个任务分配一个线程"优势更多。通过重用现有的线程而不是创建新线程,可以在处理多个请求时分摊在线程创建和销毁过程中产生的巨大开销。另一个额外的好处是,当请求到达时,工作线程通常已经存在,因此不会由于等待创建线程而延迟任务的执行,从而提高了响应性。通过适当调整线程池大小,可以创建足够多的线程以便使处理器保持忙碌,同时还可以防止过多线程相互竞争资源而使应用程序耗尽内存而失败。

ThreadPoolExecutor定义了一个线程池,该类的声明如下:

public class ThreadPoolExecutor extends AbstractExecutorService { ... }

public abstract class AbstractExecutorService implements ExecutorService { ... }

  可以看到,ThreadPoolExecutor继承自AbstractExecutorService,AbstractExecutorService实现了ExecutorService接口,所以ThreadPoolExecutor也间接实现了ExecutorService接口。

ThreadPoolExecutor定义了很多构造函数,以下代码给出了该类最重要的构造函数:

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

  corePoolSize、maximumPoolSize、keepAliveTime以及unit这几个参数分别定义了线程池的基本大小、最大大小以及存活时间。corePoolSize定义了线程池的基本大小,也就是线程池的目标大小,即在没有任务执行时线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。maximumPoolSize定义了线程池的最大大小,表示线程池可同时活动线程数量上限。keepAliveTime和unit共同定义了线程的存活时间,如果某个线程的空闲时间超过了存活时间,那么将被标记为可回收的,并且当线程池的当前大小超过基本大小时,这个线程将被终止。

workQueue参数包含Runnable的阻塞队列,当线程池达到基本大小时,新提交的任务将放入这个阻塞队列中,阻塞队列的实现包含三种:无界队列、有界队列以及同步移交队列。

threadFactory参数用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字,方便定位问题。

handler参数定义了线程池饱和策略。当有界队列被填满后,并且线程池活动线程达到最大线程数,饱和策略开始生效。JDK提供了几种不同的RejectedExecutionHandler实现,分别是AbortPolicy、DiscardPolicy、DiscardOldestPolicy以及CallerRunsPolicy。AbortPolicy是默认的饱和策略,该策略将抛出未检查的RejectedExecutionException。DiscardPolicy策略会把新提交的任务直接抛弃,而DiscardOldestPolicy策略会抛弃队列首部最老的任务。CallerRunsPolicy策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量,它不会在线程池中的某个线程中执行新提交的任务,而是在一个调用了execute的线程中执行该任务。

Executors静态工厂方法

从上一节内容看出,ThreadPoolExecutor的新建需要传入很多参数,使用起来极不方便。为了便于使用,Executors为我们提供了几个静态工厂方法,大大简化了线程池的创建,它们分别是:

  • newFixedThreadPool:newFixedThreadPool将创建一个固定大小的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程池的规模将不再变化;
  • newCachedThreadPool:newCachedThreadPool将创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求,那么将回收空闲线程;而当需求增加时,可以添加新的线程,线程池的规模不存在任何限制。
  • newSingleThreadExecutor:newSingleThreadExecutor是一个单线程的Executor,它创建单个工作者线程执行任务,如果这个线程异常结束,会创建另一个线程代替。

以newCachedThreadPool为例,我们可以看看这些静态工厂方法的内部实现:

    public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

  可以看到,这些静态工厂方法最终还是调用的ThreadPoolExecutor的构造函数,指定了线程池基本大小为0,最大大小为Integer值上限,线程存活时间为60s,阻塞队列是一个SynchronousQueue。从这些参数可以知道,当线程提交newCachedThreadPool的线程池时,由于基本大小为0,所以肯定大于基本大小,然后任务会进入阻塞队列,而SynchronousQueue内部没有任何容量,且当前线程数未达到最大线程数,所以任务将立即执行。任务执行完有60s的超时时间,如果在这段时间内有新任务调用,那么新任务将直接在这个线程上运行。

总结

线程池的使用能够帮助我们统一管理线程,提高线程的可管理性,在写多线程代码时,我们应该优先使用线程池方式创建线程。

Java并发(基础知识)—— Executor框架及线程池的更多相关文章

  1. 并发新特性—Executor 框架与线程池

    兰亭风雨 · 更新于 2018-11-14 09:00:31 并发新特性-Executor 框架与线程池 Executor 框架简介 在 Java 5 之后,并发编程引入了一堆新的启动.调度和管理线程 ...

  2. 转:【Java并发编程】之十九:并发新特性—Executor框架与线程池(含代码)

      Executor框架简介 在Java5之后,并发编程引入了一堆新的启动.调度和管理线程的API.Executor框架便是Java 5中引入的,其内部使用了线程池机制,它在java.util.coc ...

  3. 【Java并发编程】:并发新特性—Executor框架与线程池

    Executor框架简介 在Java5之后,并发编程引入了一堆新的启动.调度和管理线程的API.Executor框架便是Java 5中引入的,其内部使用了线程池机制,它在java.util.cocur ...

  4. 并发新特性—Executor框架与线程池

    http://blog.csdn.net/ns_code/article/details/17465497 Executor框架简介 在Java5之后,并发编程引入了一堆新的启动.调度和管理线程的AP ...

  5. Java 并发编程——Executor框架和线程池原理

    Eexecutor作为灵活且强大的异步执行框架,其支持多种不同类型的任务执行策略,提供了一种标准的方法将任务的提交过程和执行过程解耦开发,基于生产者-消费者模式,其提交任务的线程相当于生产者,执行任务 ...

  6. Java 并发编程——Executor框架和线程池原理

    Java 并发编程系列文章 Java 并发基础——线程安全性 Java 并发编程——Callable+Future+FutureTask java 并发编程——Thread 源码重新学习 java并发 ...

  7. Java并发(二十一):线程池实现原理

    一.总览 线程池类ThreadPoolExecutor的相关类需要先了解: (图片来自:https://javadoop.com/post/java-thread-pool#%E6%80%BB%E8% ...

  8. java并发编程笔记(七)——线程池

    java并发编程笔记(七)--线程池 new Thread弊端 每次new Thread新建对象,性能差 线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或者OOM 缺 ...

  9. Java并发编程(您不知道的线程池操作), 最受欢迎的 8 位 Java 大师,Java并发包中的同步队列SynchronousQueue实现原理

    Java_并发编程培训 java并发程序设计教程 JUC Exchanger 一.概述 Exchanger 可以在对中对元素进行配对和交换的线程的同步点.每个线程将条目上的某个方法呈现给 exchan ...

  10. Java并发编程(您不知道的线程池操作)

    Java并发编程(您不知道的线程池操作) 这几篇博客,一直在谈线程,设想一下这个场景,如果并发的线程很多,然而每个线程如果执行的时间很多的话,这样的话,就会大量的降低系统的效率.这时候就可以采用线程池 ...

随机推荐

  1. centOS 开启端口

    生产环境禁止关闭防火墙,只能开端口 [root@BetaD home]# firewall-cmd --add-port=/tcp --permanent [root@BetaD home]# fir ...

  2. CentOS7 执行yum 命令出错

    http://blog.csdn.net/weicuidi/article/details/52935100 https://www.cnblogs.com/pqchao/p/6428947.html

  3. 非线性函数图像表示(GLSL)

    说明:绘图区域x轴(0->1),y轴(0->1); 1.y = 0.5 + sqrt(x * (1 - x)) 2.y = smoothstep(a , b , x) y = smooth ...

  4. Spring Boot(2)中的yaml配置简介

    搞Spring Boot的小伙伴都知道,Spring Boot中的配置文件有两种格式,properties或者yaml,一般情况下,两者可以随意使用,选择自己顺手的就行了,那么这两者完全一样吗?肯定不 ...

  5. nginx集群+mysql数据同步

    mysql集群配置在网站负载均衡中是必不可少的: 首先说下我个人准备的负载均衡方式: 1.通过nginx方向代理来将服务器压力分散到各个服务器上: 2.每个服务器中代码逻辑一样: 3.通过使用redi ...

  6. ipcs查看消息队列命令

    修改消息队列大小: root:用户: /etc/sysctl.conf kernel.msgmnb =4203520 #kernel.msgmnb =3520 kernel.msgmni = 2878 ...

  7. 第五周课程总结&试验报告三

    第五周课程总结 一.第五周课程总结 1.this关键字 this可用于任何实例方法内指向当前对象,也可指向对其调用当前方法的对象,或者在需要当前类型对象引用时使用.当一个类的属性(成员变量)名与访问该 ...

  8. jenkins和gitlab备份

    #把/opt目录拷贝到10.0.0.31的/tmp下. rsync  -avz  /opt  root@10.0.0.31:/tmp/ pull: rsync -avz root@10.0.0.31: ...

  9. 【基本优化实践】【1.1】IO优化——把文件迁移到不同物理磁盘

    [1]概念 把不同数据文件移动到不同的物理磁盘,无疑是一个提高IO的有效办法 在资源可以的情况下,尽量把 temp .数据库的主数据文件(mdf).数据库的从数据数据(ndf).数据库的事务日志文件( ...

  10. mapreduce运行的bug收录

    在8088端口可以看到日志文件(主要看error),操作如下: 1.window jdk版本最好和linux jdk 版本一致,不然容易出现莫名奇妙的bug 之前出现一个bug: Unsupporte ...