Java的一大优势是能完成多线程任务,对线程的封装和调度非常好,那么它又是如何实现的呢?

jdk的包下和线程相关类的类图。

从上面可以看出Java的线程池主的实现类主要有两个类ThreadPoolExecutorForkJoinPool

ForkJoinPoolFork/Join框架下使用的一个线程池,一般情况下,我们使用的比较多的就是ThreadPoolExecutor。我们大多数时候创建线程池是通过Executors类的几个方法实现的:

  • newFixedThreadPool():创建一个固定线程数的线程池,可控制线程最大并发数,适用需要限制线程池数量的应用场景。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
} public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
  • newSingleThreadExecutor():创建一个单线程的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO)执行适用于那种需要按照线程数量执行的场景。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
} public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
  • newCachedThreadPool():创建一个可以根据需要创建新线程的线程池,它是没有线程数量限制的,适用于短期异步任务的操作,或者是负载比较轻的服务器。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
} public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
  • newScheduledThreadPool():创建一个固定线程数的线程池,支持定时及周期性执行后台任务。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
} public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}

这里看到基本上这几个方法都是返回了ThreadPoolExecutor这个对象。以下是ThreadPoolExecutor的构造方法:

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

几个参数的含义:

  1. corePoolSize(线程池基本大小):当提交一个新任务到线程池,线程会创建一个新的线程来执行任务,无论其他的基本线程是否是空闲的状态。这种情况会持续到当需要执行的任务数量大于线程池基本线程数量大小时就不再创建了。
  2. maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数量,如果队列满了,并且已经创建的线程数小于最大线程数量,这个线程池会再创建新的线程执行任务。
  3. KeepAliveTime(线程保持活动的时间):线程空闲之后保持存活的时间。
  4. TimeUnit(线程保持活动时间的单位):可以使用TimeUnit时间单位来设置。
  5. runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列,可以选择以下几个:
    • ArrayBlockingQueue:基于数组的阻塞队列,按照FIFO原则进行排序
    • LinkedBlockingQueue:基于链表的阻塞队列,按照FIFO原则对元素进行排序,吞吐量高于ArrayBlockingQueue。Executors.newFixedThreadPool()使用了这个队列。
    • SynchronousQueue:一个不储存元素的阻塞队列,每一个插入操作必须等到另外一个线程调用移除操作,否则插入操作一直处于阻塞状态。吞吐量高于LinkedBlockingQueue,Executors.newCachedThreadPool使用了这个队列。
    • PriorityBlockingQueue:一个具有优先级的无限阻塞队列
  6. RejectedExecutionHandler(饱和策略):这个本身是Java的一个接口,当队列和线程池都满了,需要一种策略处理新的任务,在这个类的最下部提供了四种内置的实现类:
    • AbortPolicy:直接抛出异常。
    • CallerRunsPolicy:只用调用者所在的线程来运行任务。
    • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前的任务。
    • DiscardPolicy:不处理,直接丢弃。
    • 自定义策略:实现RejectedExecutionHandler接口,自定义策略。
  7. ThreadFactory(线程工厂):用于设置创建新的线程的工厂。可以使用guava的ThreadFactoryBuilder来创建一个ThreadFactory。

线程池用的最多的是execute(),它的执行实际上分了三步:

  1. 当少量的线程在运行,线程的数量还没有达到corePoolSize,那么启用新的线程来执行新任务。
  2. 如果线程数量已经达到了corePoolSize,那么尝试把任务缓存起来,然后二次检查线程池的状态,看这个时候是否能添加一个额外的线程,来执行这个任务。如果这个检查到线程池关闭了,就拒绝任务。
  3. 如果我们没法缓存这个任务,那么我们就尝试去添加线程去执行这个任务,如果失败,可能任务已被取消或者任务队列已经饱和,就拒绝掉这个任务。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}

线程池把每个线程都封装成一个对象Worker,以上有一个关键的函数addWorker():

addWorker(Runnable firstTask, boolean core)

firstTask代表这个线程池首先要执行的任务,core代表是否使用corePoolSize来做为线程池线程的最大标记。

以上就是对线程池的一个基本解析。

Java线程池解析的更多相关文章

  1. 面试必备:Java线程池解析

    前言 掌握线程池是后端程序员的基本要求,相信大家求职面试过程中,几乎都会被问到有关于线程池的问题.我在网上搜集了几道经典的线程池面试题,并以此为切入点,谈谈我对线程池的理解.如果有哪里理解不正确,非常 ...

  2. java线程池与五种常用线程池策略使用与解析

    背景:面试中会要求对5中线程池作分析.所以要熟知线程池的运行细节,如CachedThreadPool会引发oom吗? java线程池与五种常用线程池策略使用与解析 可选择的阻塞队列BlockingQu ...

  3. Java线程池源码解析

    线程池 假如没有线程池,当存在较多的并发任务的时候,每执行一次任务,系统就要创建一个线程,任务完成后进行销毁,一旦并发任务过多,频繁的创建和销毁线程将会大大降低系统的效率.线程池能够对线程进行统一的分 ...

  4. 并发编程(十二)—— Java 线程池 实现原理与源码深度解析 之 submit 方法 (二)

    在上一篇<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中提到了线程池ThreadPoolExecutor的原理以及它的execute方法.这篇文章是接着上一篇文章 ...

  5. 沉淀再出发:java中线程池解析

    沉淀再出发:java中线程池解析 一.前言 在多线程执行的环境之中,如果线程执行的时间短但是启动的线程又非常多,线程运转的时间基本上浪费在了创建和销毁上面,因此有没有一种方式能够让一个线程执行完自己的 ...

  6. java线程池和五种常用线程池的策略使用与解析

    java线程池和五种常用线程池策略使用与解析 一.线程池 关于为什么要使用线程池久不赘述了,首先看一下java中作为线程池Executor底层实现类的ThredPoolExecutor的构造函数 pu ...

  7. 含源码解析,深入Java 线程池原理

    从池化技术到底层实现,一篇文章带你贯通线程池技术. 1.池化技术简介 在系统开发过程中,我们经常会用到池化技术来减少系统消耗,提升系统性能. 在编程领域,比较典型的池化技术有: 线程池.连接池.内存池 ...

  8. 四种Java线程池用法解析

    本文为大家分析四种Java线程池用法,供大家参考,具体内容如下 http://www.jb51.net/article/81843.htm 1.new Thread的弊端 执行一个异步任务你还只是如下 ...

  9. 并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)

    史上最清晰的线程池源码分析 鼎鼎大名的线程池.不需要多说!!!!! 这篇博客深入分析 Java 中线程池的实现. 总览 下图是 java 线程池几个相关类的继承结构:    先简单说说这个继承结构,E ...

随机推荐

  1. Cmder--Windows下命令行利器

    cmder cmder是一个增强型命令行工具,不仅可以使用windows下的所有命令,更爽的是可以使用linux的命令,shell命令. 安装包 安装包链接 下载后,直接解压即用. 修改命令提示符λ为 ...

  2. Python编码记录

    字节流和字符串 当使用Python定义一个字符串时,实际会存储一个字节串: "abc"--[97][98][99] python2.x默认会把所有的字符串当做ASCII码来对待,但 ...

  3. 消息队列 Kafka 的基本知识及 .NET Core 客户端

    前言 最新项目中要用到消息队列来做消息的传输,之所以选着 Kafka 是因为要配合其他 java 项目中,所以就对 Kafka 了解了一下,也算是做个笔记吧. 本篇不谈论 Kafka 和其他的一些消息 ...

  4. PHP之时间和日期函数

    // 时间日期函数 Time <?php date_default_timezone_set('UTC'); // 获取当前时间的时间戳 $time0 = mktime(); $time1 = ...

  5. 深入node之Transform

    Transform流特性 在开发中直接接触Transform流的情况不是很多,往往是使用相对成熟的模块或者封装的API来完成流的处理,最为特殊的莫过于through2模块和gulp流操作.那么,Tra ...

  6. MyBatis基础入门--知识点总结

    对原生态jdbc程序的问题总结 下面是一个传统的jdbc连接oracle数据库的标准代码: public static void main(String[] args) throws Exceptio ...

  7. 一款开源免费的.NET文档操作组件DocX(.NET组件介绍之一)

    在目前的软件项目中,都会较多的使用到对文档的操作,用于记录和统计相关业务信息.由于系统自身提供了对文档的相关操作,所以在一定程度上极大的简化了软件使用者的工作量. 在.NET项目中如果用户提出了相关文 ...

  8. Java类变量和成员变量初始化过程

    一.类的初始化 对于类的初始化:类的初始化一般只初始化一次,类的初始化主要是初始化静态成员变量. 类的编译决定了类的初始化过程. 编译器生成的class文件主要对定义在源文件中的类进行了如下的更改: ...

  9. 热修复-Nuwa学习篇

    nuwa热修复是基于qq空间团队的思路,最近的热度话题了,很多种方案,自己先研究几种方案,基本上都各有优势,学习肯定得先挑个软柿子捏了,自己对比了一下,发现nuwa代码量少点,所以就决定了,先研究nu ...

  10. 敏捷转型历程 - Sprint3 回顾会

    我: Tech Leader 团队:团队成员分布在两个城市,我所在的城市包括我有4个成员,另外一个城市包括SM有7个成员.另外由于我们的BA离职了,我暂代IT 的PO 职位.PM和我在一个城市,但他不 ...