JDK默认提供了四种线程池:SingleThreadExecutor、FiexdThreadPool、CachedThreadPool、ScheduledThreadPoolExecutor。

本文会先从前三个线程池的使用开始讲解,然后过度到线程池参数、拒绝策略等方面进行全面讲解,最后自己根据参数构造一个

线程池。

SingleThreadExecutor

    public static void singleThreadExecutorTest() {
ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.execute(()->{
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
executorService.execute(()->{
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
executorService.execute(()->{
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}); try {
Thread.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdown();
}

该代码是使用Exectors工具类创建的一个大小为1的线程池,并且创建三个任务提交到该线程执行,每个任务执行需要一秒。因此从运行结果中我们可以很清楚的看到三个任务一次只能执行一个线程。

FiexdThreadPool

public static void fixedThreadPoolTest() {
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(()->{
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
service.execute(()->{
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
service.execute(()->{ System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}); try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.shutdown();
}

该代码是使用Exectors工具类创建的一个大小为2的线程池,并且创建三个任务提交到该线程执行,每个任务执行需要一秒。从结果可以看到第一秒两个线程一起执行了,第二秒第三个线程才执行。

CachedThreadPool

public static void cachedThreadPoolTest() {
ExecutorService service = Executors.newCachedThreadPool();
service.execute(()->{
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
service.execute(()->{
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
service.execute(()->{ System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}); try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.shutdown();
}

该代码是使用Exectors工具类创建的一个大小为Integer.MAX_VALUE的线程池,并且创建三个任务提交到该线程执行,每个任务执行需要一秒。从结果可以看到三个线程一起执行了。

原理分析

构造SingThreadExecutor需要的参数

public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>
()));
}

构造FiexdThreadPool需要的参数

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

构造CachedThreadPool需要的参数

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

通过上面三个线程池构造的源码我们可以看到,创建线程池的时候都是通过创建ThreadPoolExecutor对象,创建ThreadPoolExecutor需要5个参数,那么这5个参数是什么呢,我们继续跟踪源码

构造ThreadPoolExecutor对象需要的参数

 public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler
);
}

这里可以看到生成ThreadPoolExecutor对象需要为其构造参数传递5个参数,但是下面的this里却是7个参数。原来有两个参数是已经固定好的。我们跟踪进去看看这七个参数

ThreadPoolExecutor构造器源码

到这里重头戏来了,下面这个代码才是真真正在创建ThreadPoolExecutor对象的构造器源码。我们可以看到一共有7个参数,我们来谈谈这七个参数

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

创建线程池需要的七个参数

int corePoolSize : 常驻核心线程数

int maximumPoolSize:最大线程数

long keepAliveTime:除了常驻线程的额外线程的存活时间

TimeUnit unit:存活时间的单位

BlockingQueue<Runnable> workQueue:任务队列

ThreadFactory threadFactory:创建线程的工厂

RejectedExecutionHandler handler:拒绝策略

我们先眼熟一下这七个参数,接下来我们从线程池的工作流程来讲解这七个参数

线程池工作流程

线程池刚创建的时候里面是空的,这时候还没有线程。当main线程往线程池里面加任务的时候才会开始创线程

1 主线程往线程池里添加任务,发现核心线程为空,创建核心线程执行任务

2 随着主线程往线程池里加任务,核心线程都被占用了,这时任务会被放入任务队列

3 随着主线程继续往线程池里加任务,任务队列也满了,线程池会创建额外线程执行任务。

4 主线程继续添加任务,任务队列满了,核心线程满了。额外线程+核心线程=总线程数,即额外线程也满了,这时候就会执行拒绝策略。

5 如果没有到4那么极端,随着任务的执行,任务队列越来越少,直至没有,那么额外线程在等待keepaliveTime时间后被销毁,线程池里只剩下核心线程存在。

相信知道了流程以后,上面的7个参数我们也就知道都是什么了,这里需要注意的一点是核心线程数是包含在总线程数里面。

谈谈JDK自带的线程池弊端

从上面我们可以看到,JDK自带的SingleThreadExecutor、FixedThreadPool都是采用LinkedBlockingQueue阻塞队列作为任务队列,由于LinkedBlockingQueue是一个

大小为Integer.MAX_VALUE的阻塞队列,因此main线程在添加任务的时候阻塞队列不会满,也就是不会触发拒绝策略,可能会导致任务持续添加引发OOM。而CachedThreadPool

的最大线程数为Integer.MAX_VALUE,会无线创建线程,来一个任务创一个线程,可能会导致线程创太多导致OOM所有我们一般都是自己构造参数创线程池。

四大拒绝策略

AbortPolicy:当任务队列和线程达到最大线程数后还添加任务的话会直接抛异常,阻止程序运行

CallerRunsPolicy:当任务队列和线程达到最大线程数后还添加任务不会抛异常和抛弃任务,而是会将任务回退给调用者。

DiscardOldestPolicy:当任务队列和线程达到最大线程数后还添加任务的话会将任务队列中等待最久的一个任务抛弃掉然后添加新任务

DiscardPolicy:直接抛弃任务。不做任何处理也不抛异常

自己构造线程池

该线程池核心线程数为2,最大线程数为5,额外线程存活时间为60秒,阻塞队列大小为3,采用默认的线程工厂,采用默认的拒绝策略,即抛异常

因此该线程池最多可以连续提供8个任务,超过8个很可能促发拒绝策略,抛异常

public static void myThreadPool() {

        ExecutorService threadPool = new ThreadPoolExecutor(2,
5,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()); for(int i=0;i<8;i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadPool.shutdown();
}

线程池参数如何选择

CPU密集型:CPU密集型CPU使用率高,一直在工作,空闲时间少,因此最大线程数最好设为CPU核数+1

IO密集型:IO密集型大部分时间都在做IO操作,CPU空闲时间多,因此最大线程数最好设为CPU核数*2.

JUC线程池深入刨析的更多相关文章

  1. 死磕 java线程系列之线程池深入解析——普通任务执行流程

    (手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本. 注:线程池源码部分如无特殊说明均指ThreadPoolExecutor类. 简介 前面我们一起学习了Java中 ...

  2. Java并发编程与技术内幕:线程池深入理解

    摘要: 本文主要讲了Java当中的线程池的使用方法.注意事项及其实现源码实现原理,并辅以实例加以说明,对加深Java线程池的理解有很大的帮助. 首先,讲讲什么是线程池?照笔者的简单理解,其实就是一组线 ...

  3. Java线程池深入理解

    之前面试baba系时遇到一个相对简单的多线程编程题,即"3个线程循环输出ADC",自己答的并不是很好,深感内疚,决定更加仔细的学习<并发编程的艺术>一书,到达掌握的强度 ...

  4. 转:Java并发编程与技术内幕:线程池深入理解

    版权声明:本文为博主林炳文Evankaka原创文章,转载请注明出处http://blog.csdn.net/evankaka 目录(?)[+] ); } catch (InterruptedExcep ...

  5. 原创:ThreadPoolExecutor线程池深入解读(一)----原理+应用

    本文档,适合于对多线程有一定基础的开发人员.对多线程的一些基础性的解读,请参考<java并发编程>的前5章. 对于源代码的解读,本人认为可读可不读.如果你想成为一位顶级的程序员,那就培养自 ...

  6. 死磕 java线程系列之线程池深入解析——体系结构

    (手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本. 简介 Java的线程池是块硬骨头,对线程池的源码做深入研究不仅能提高对Java整个并发编程的理解,也能提高自己 ...

  7. 死磕 java线程系列之线程池深入解析——生命周期

    (手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本. 注:线程池源码部分如无特殊说明均指ThreadPoolExecutor类. 简介 上一章我们一起重温了下线程的 ...

  8. 死磕 java线程系列之线程池深入解析——未来任务执行流程

    (手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本. 注:线程池源码部分如无特殊说明均指ThreadPoolExecutor类. 简介 前面我们一起学习了线程池中普 ...

  9. 死磕 java线程系列之线程池深入解析——定时任务执行流程

    (手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本. 注:本文基于ScheduledThreadPoolExecutor定时线程池类. 简介 前面我们一起学习了普通 ...

随机推荐

  1. np.newaxis()用法

    这个是liaspace函数 这个是np.newaxis的用法,增加维度,写一个表示增加一维,两个表示增加2维2位置的:号是对a的取值范围,如果把np.newaxis作为第一个参数是对行增加维度,作为第 ...

  2. Python框架之Django学习笔记(十二)

    Django站点管理 十一转眼结束,说好的充电没能顺利开展,反而悠闲的看了电视剧以及去影院看了新上映的<心花路放>.<亲爱的>以及<黄金时代>,说好的劳逸结合现在回 ...

  3. 3、CSS基础 part-1

    1.给body设置颜色 <html> <body text="red"> <p> hello world</p> <p> ...

  4. IOS开发学习笔记043-QQ聊天界面实现

    QQ聊天界面实现 效果如下: 实现过程: 1.首先实现基本界面 头像使用 UIImageView : 文字消息使用 UIButton 标签使用 UILable :水平居中 所有元素在一个cell中,在 ...

  5. python 中单例模式

    1.什么是单例模式: 单例模式是指一个类有且只有一个实例对象,创建一个实例对象后,再创建实例是返回上一次的对象引用.(简单的讲就是两个实例对象的ID相同,节省了内存空间) 2.单例模式的创建: 举例创 ...

  6. structs2 对ActionContext valueStack stack context 的理解 图片实例

    structs2 对ActionContext valueStack stack context 的理解 ActionConext : The ActionContext is the context ...

  7. order by 对null的处理

    [Oracle 结论] order by colum asc 时,null默认被放在最后order by colum desc 时,null默认被放在最前nulls first 时,强制null放在最 ...

  8. 关于JavaWeb开发的一些感悟

    从事JavaWeb的开发已经三年了,从最开始的啥都不会,到慢慢的能够独立做项目,从一开始的一片茫然,到现在的心中有数.对于技术.业务也有了自己的看法. JavaWeb开发所涉及到的知识点非常多,涉及到 ...

  9. (转)iOS-蓝牙学习资源博文收集

    ios蓝牙开发(一)蓝牙相关基础知识 ios蓝牙开发(二)蓝牙中心模式的ios代码实现 ios蓝牙开发(三)app作为外设被连接的实现 ios蓝牙开发(四)BabyBluetooth蓝牙库介绍 暂未完 ...

  10. js作用域的理解

    script:自上而下 全局变量.全局函数 函数:由里到外 浏览器: “JS解析器” 1)“找一些东西”: var function 参数 a = undefine 所有的变量,在正式运行代码之前,都 ...