专栏首页小强的进阶之路没有分析过线程池源码 ,谁给你勇气去面试

没有分析过线程池源码 ,谁给你勇气去面试

作者:若丨寒 https://www.jianshu.com/p/bc978c220da6

前言

线程池源码也是面试经常被提问到的点,我会将全局源码做一分析,然后告诉你面试考啥,怎么答。

为什么要用线程池?

简洁的答两点就行。

  1. 降低系统资源消耗。
  2. 提高线程可控性。

如何创建使用线程池?

JDK8提供了五种创建线程池的方法:

1.创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

2.(JDK8新增)会根据所需的并发数来动态创建和关闭线程。能够合理的使用CPU进行对任务进行并发操作,所以适合使用在很耗时的任务。

注意返回的是ForkJoinPool对象。

什么是ForkJoinPool:

使用一个无限队列来保存需要执行的任务,可以传入线程的数量;不传入,则默认使用当前计算机中可用的cpu数量;使用分治法来解决问题,使用fork()和join()来进行调用。

3.创建一个可缓存的线程池,可灵活回收空闲线程,若无可回收,则新建线程。

4.创建一个单线程的线程池。

5.创建一个定长线程池,支持定时及周期性任务执行。

上层源码结构分析

Executor结构:

Executor

一个运行新任务的简单接口

ExecutorService

扩展了Executor接口。添加了一些用来管理执行器生命周期和任务生命周期的方法

AbstractExecutorService

对ExecutorService接口的抽象类实现。不是我们分析的重点。

ThreadPoolExecutor

Java线程池的核心实现。

ThreadPoolExecutor源码分析

属性解释

值的注意的是状态值越大线程越不活跃。

线程池状态的转换模型:

构造器

在向线程池提交任务时,会通过两个方法:execute和submit。

本文着重讲解execute方法。submit方法放在下次和Future、Callable一起分析。

execute方法:

总结一下它的工作流程:

  1. workerCount < corePoolSize,创建线程执行任务。
  2. workerCount >= corePoolSize&&阻塞队列workQueue未满,把新的任务放入阻塞队列。
  3. workQueue已满,并且workerCount >= corePoolSize,并且workerCount < maximumPoolSize,创建线程执行任务。
  4. 当workQueue已满,workerCount >= maximumPoolSize,采取拒绝策略,默认拒绝策略是直接抛异常。

通过上面的execute方法可以看到,最主要的逻辑还是在addWorker方法中实现的,那我们就看下这个方法:

addWorker方法

主要工作是在线程池中创建一个新的线程并执行

参数定义:

  • firstTask the task the new thread should run first (or null if none). (指定新增线程执行的第一个任务或者不执行任务)
  • core if true use corePoolSize as bound, else maximumPoolSize.(core如果为true则使用corePoolSize绑定,否则为maximumPoolSize。 (此处使用布尔指示符而不是值,以确保在检查其他状态后读取新值)。)
为什么需要持有mainLock?

因为workers是HashSet类型的,不能保证线程安全。

w = new Worker(firstTask);如何理解呢

Worker.java

可以看到它继承了AQS并发框架还实现了Runnable。证明它还是一个线程任务类。那我们调用t.start()事实上就是调用了该类重写的run方法。

Worker为什么不使用ReentrantLock来实现呢?

tryAcquire方法它是不允许重入的,而ReentrantLock是允许重入的。对于线程来说,如果线程正在执行是不允许其它锁重入进来的。

线程只需要两个状态,一个是独占锁,表明正在执行任务;一个是不加锁,表明是空闲状态。

run方法又调用了runWorker方法:

总结一下runWorker方法的执行过程:

1、while循环中,不断地通过getTask()方法从workerQueue中获取任务

2、如果线程池正在停止,则中断线程。否则调用3.

3、调用task.run()执行任务;

4、如果task为null则跳出循环,执行processWorkerExit()方法,销毁线程workers.remove(w);

这个流程图非常经典:

除此之外,ThreadPoolExector还提供了tryAcquiretryReleaseshutdownshutdownNowtryTerminate、等涉及的一系列线程状态更改的方法有兴趣可以自己研究。大体思路是一样的,这里不做介绍。

Worker为什么不使用ReentrantLock来实现呢?

tryAcquire方法它是不允许重入的,而ReentrantLock是允许重入的。对于线程来说,如果线程正在执行是不允许其它锁重入进来的。

线程只需要两个状态,一个是独占锁,表明正在执行任务;一个是不加锁,表明是空闲状态。

在runWorker方法中,为什么要在执行任务的时候对每个工作线程都加锁呢?

shutdown方法与getTask方法存在竞态条件.(这里不做深入,建议自己深入研究,对它比较熟悉的面试官一般会问)

高频考点

  1. 创建线程池的五个方法。
  2. 线程池的五个状态
  3. execute执行过程。
  4. runWorker执行过程。(把两个流程图记下,理解后说个大该就行。)
  5. 比较深入的问题就是我在文中插入的问题。
  6. …期望大家能在评论区补充。

End

本文分享自微信公众号 - 小强的进阶之路(xiaoqiang_code)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-06-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Executor线程池只看这一篇就够了

    注意:启动Thread线程只能用start(JNI方法)来启动,start方法通知虚拟机,虚拟机通过调用器映射到底层操作系统,通过操作系统来创建线程来执行当前任...

    程序员小强
  • 【基本功】不可不说的Java“锁”事

    Java提供了种类丰富的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率。本文旨在对锁相关源码(本文中的源码来自JDK 8)、使用场景进行举例,为...

    程序员小强
  • 带你用生活大白话理解 NIO

    今晚是个下雨天,写完今天最后一行代码,小强起身合上电脑,用滚烫的开水为自己泡制了一桶老坛酸菜牛肉面。这大概是苦逼程序猿给接下来继续奋战的自己最好的馈赠。年轻的程...

    程序员小强
  • 1.多线程-了解多线程与高并发

    一个CPU,去执行一个多线程任务。是不可能并行的,一个CPU只能执行一条命令,CPU会高速的切换线程任务去执行。这种情况下线程是并发的。 一个系统中拥有多个C...

    杨小杰
  • Java中的线程 Krains 2020-08-24

    Java线程之间的通信由Java内存模型(简称JMM)控制,从抽象的角度来说,JMM定义了线程和主内存之间的抽象关系。

    Krains
  • Java 多线程编程(“锁”事碎碎念)

    对于多个线程间的共享数据,悲观锁认为自己在使用数据的时候很有可能会有其它线程也刚好前来修改数据,因为在使用数据前都会加上锁,确保在使用过程中数据不会被其它线程修...

    叶志陈
  • Java 多线程编程(聊聊线程池)

    线程是一种昂贵的系统资源,其“昂贵”不仅在于创建线程所需要的资源开销,还在于使用过程中带来的资源消耗。一个系统能够支持同时运行的线程总数受限于该系统所拥有的处理...

    叶志陈
  • 编程体系结构(05):Java多线程并发

    线程是操作系统能够进行运算调度的最小单位,包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程...

    知了一笑
  • 新手一看就懂的线程池

    线程池是帮助我们管理线程的工具,它维护了多个线程,可以降低资源的消耗,提高系统的性能。

    好好学java
  • Java中线程池的参数有几个?

    在使用线程池时,为了获取最佳的性能,常常需要手动指定线程池的参数,ThreadPoolExecutor是最常用的线程池执行器,它有四个构造方法,参数最多的构造方...

    小诸葛

扫码关注云+社区

领取腾讯云代金券