一 Executor框架

为了更好地控制多线程,JDK提供了一套线程框架Executor,帮助开发人员有效的进行线程控制。它们都在java.util.concurrent包中,是JDK并发包的核心。其中有一个比较重要的类:Executors,它扮演着线程工厂的角色,我们通过Executors可以创建特定功能的线程池。

Executors创建线程池方法:

  1. newFixedThreadPool():该方法返回一个固定数量的线程池,该方法的线程数始终不变,当有一个在任务提交时,若线程池中空闲,则立即执行,若没有,则会被暂缓在一个任务队列中等待有空闲的线程去执行
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
  1. newSingleThreadExecutor():创建数量为一个线程的线程池,若空闲则执行,若没有空闲线程则暂缓在任务队列中
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
  1. newCacheThreadPool():返回一个可根据实际情况调整线程个数的线程池,不限制最大线程数量,若有任务则创建线程去执行,若无任务则不继续创建线程,并且每一个空闲线程会在60秒后自动回收
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
  1. newScheduledThreadPool():返回一个ScheduleExecutorService对象,但该线程池可以指定线程的数量。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}

使用例子:

class Temp extends Thread {
@Override
public void run() {
System.out.println("run...");
}
} public class ScheduledJob { public static void main(String[] args) {
Temp command = new Temp();
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.scheduleWithFixedDelay(command, 1, 3, TimeUnit.SECONDS);
//1秒后执行线程,之后每隔3秒轮询
}
}

运行结果:

run...

run...

run...

run...

run...

run...

run...

...

1.1 自定义线程池

若Executors工厂类无法满足我们的需求,可以自己去创建自定义的线程池。其实Executors工厂类里面的创建线程方法其内部实现均是用了ThreadPoolExecutor这个类,这个类可以自定义线程,构造方法如下:

public ThreadPoolExecutor(int corePoolSize,    //表示当前创建的核心线程数
int maximumPoolSize, //表示最大线程数
long keepAliveTime, //线程池空闲时存活时间
TimeUnit unit, //指定时间单位
BlockingQueue<Runnable> workQueue, //缓存队列
ThreadFactory threadFactory, //
RejectedExecutionHandler handler) { //拒绝执行的方法 ... ... }

这个构造方法对于队列是什么类型的比较关键:

  1. 使用有界任务队列时:若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程;若大于corePoolSize,则会将任务加入队列,若队列已满,则在总线程数不大于maximumPoolSize的前提下,创建新的线程,若线程数大于maximumPoolSize,则执行拒绝策略。或其他自定义方式
public class UserThreadPoolExecutor {

	public static void main(String[] args) {
/**
* 使用有界任务队列时:若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程;
* 若大于corePoolSize,则会将任务加入队列,
* 若队列已满,则在总线程数不大于maximumPoolSize的前提下,创建新的线程,
* 若线程数大于maximumPoolSize,则执行拒绝策略。
*/
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1, //coreSize
2, //maxSize
60, //无效时间
TimeUnit.SECONDS, //单位
new ArrayBlockingQueue<Runnable >(3) //有界队列
);
pool.execute(new MyTask(1, "任务1"));
pool.execute(new MyTask(2, "任务2"));
pool.execute(new MyTask(3, "任务3"));
pool.execute(new MyTask(4, "任务4"));
pool.execute(new MyTask(5, "任务5"));
pool.execute(new MyTask(6, "任务6"));
pool.shutdown();
} }

运行结果:

run taskId = 1

run taskId = 5

Exception in thread "main"

java.util.concurrent.RejectedExecutionException: Task Thread[Thread-5,5,main] rejected from java.util.concurrent.ThreadPoolExecutor@70dea4e[Running, pool size = 2, active threads = 2, queued tasks = 3, completed tasks = 0]

at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)

at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)

at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)

at Executor.UserThreadPoolExecutor.main(UserThreadPoolExecutor.java:28)

run taskId = 2

run taskId = 3

run taskId = 4

分析:

任务数大于coreSize(目前为1),则有任务加入队列,又队列(队列容量为3)已满,则创建一个线程(目前coreSize为2),由于maxSize为2,所有最多只能再创建一个线程到线程池(目前coreSize+queue=5小于任务数6),无法再创建线程,执行拒绝策略

  1. 使用无界任务队列时:LinkedBlockingQueue。与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在入队失败的情况。当有新任务到来,系统线程数小于corePoolSize时,则新建线程执行任务。当达到corePoolSize后,就不会继续增加。若后续仍有新的任务加入,而又没有空闲的线程资源,则任务直接进入队列等待。若任务创建和处理的速度差异很大,无界队列会保持快速增长,知道耗尽系统内存。
public class UserThreadPoolExecutor2 implements Runnable{

	/**
* 当有新任务到来,系统线程数小于corePoolSize时,则新建线程执行任务,
* 当达到corePoolSize后,就不会继续增加,
* 若后续仍有新的任务加入,而又没有空闲的线程资源,则任务直接进入队列等待。
* 若任务创建和处理的速度差异很大,无界队列会保持快速增长,知道耗尽系统内存
*/
private static AtomicInteger count = new AtomicInteger(0); public static void main(String[] args) throws InterruptedException {
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
ThreadPoolExecutor pool = new ThreadPoolExecutor(
5,
10,
120L, //2分钟
TimeUnit.SECONDS,
queue
);
for (int i = 0; i < 20; i++) {
pool.execute(new UserThreadPoolExecutor2());
}
Thread.sleep(1000);
System.out.println("queue size : " + queue.size());
Thread.sleep(2000);
//pool.shutdown();
} @Override
public void run() {
try {
int num = count.incrementAndGet();
System.out.println("任务" + num);
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

运行结果:

任务2

任务4

任务1

任务3

任务5

queue size : 15

任务6

任务8

任务7

任务9

任务10

任务11

任务12

任务13

任务14

任务15

任务16

任务18

任务19

任务17

任务20

JDK拒绝策略:

  1. AbortPolicy:直接抛出异常,系统正常工作
  2. CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务
  3. DiscardOldestPolicy:丢弃最老的一个请求,尝试再次提交当前任务
  4. DiscardPolicy:丢弃无法处理的任务,不给于任何处理

如果需要自定义拒绝策略,可以实现RejectedExecutionHandle接口:

public class MyReject implements RejectedExecutionHandler{

	@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("自定义处理...");
System.out.println("当前被拒绝任务为:" + r.toString());
//记录日志,等待其他时间处理
}
} public class UserThreadPoolExecutor1 { public static void main(String[] args) throws InterruptedException {
/**
* 使用有界任务队列时:若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程;
* 若大于corePoolSize,则会将任务加入队列,
* 若队列已满,则在总线程数不大于maximumPoolSize的前提下,创建新的线程,
* 若线程数大于maximumPoolSize,则执行拒绝策略。
*/ ThreadPoolExecutor pool = new ThreadPoolExecutor(
1, //coreSize
2, //maxSize
60, //无效时间
TimeUnit.SECONDS, //单位
new ArrayBlockingQueue<Runnable>(3), //有界队列
new MyReject()
);
pool.execute(new MyTask(1, "任务1"));
pool.execute(new MyTask(2, "任务2"));
pool.execute(new MyTask(3, "任务3"));
pool.execute(new MyTask(4, "任务4"));
pool.execute(new MyTask(5, "任务5"));
pool.execute(new MyTask(6, "任务6")); pool.shutdown();
} }

运行结果:

自定义处理...

当前被拒绝任务为:Thread[Thread-5,5,main]

run taskId = 5

run taskId = 1

run taskId = 2

run taskId = 3

run taskId = 4

线程池——Executors的更多相关文章

  1. 线程池-Executors

    合理使用线程池能够带来三个好处 减少创建和销毁线程上所花的时间以及系统资源的开销 提高响应速度.当任务到达时,任务可以不需要等到线程创建就能立即执行 提高线程的客观理性.线程是稀缺资源,如果无限制的创 ...

  2. Java实现“睡排序”——线程池Executors的使用

    前提 之前在知乎上看见一个有意思的排序算法——睡排序. 睡排序最早好像是4chan上一个用户用shell脚本实现的: 算法思想简洁明了:利用进程的sleep来实现 越大的数字越迟输出. 虽然像2L说的 ...

  3. 线程池Executors探究

    线程池用到的类在java.util.concurrent包下,核心类是Executors,通过其不同的几个方法可产生不同的线程池. 1.生成固定大小的线程池 public static Executo ...

  4. java 线程之executors线程池

    一.线程池的作用 平时的业务中,如果要使用多线程,那么我们会在业务开始前创建线程,业务结束后,销毁线程.但是对于业务来说,线程的创建和销毁是与业务本身无关的,只关心线程所执行的任务.因此希望把尽可能多 ...

  5. 为什么阿里巴巴要禁用Executors创建线程池?

    作者:何甜甜在吗 juejin.im/post/5dc41c165188257bad4d9e69 看阿里巴巴开发手册并发编程这块有一条:线程池不允许使用Executors去创建,而是通过ThreadP ...

  6. 为什么尽量不要使用Executors创建线程池

    看阿里巴巴开发手册并发编程这块有一条:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,通过源码分析禁用的原因. 线程池的优点 管理一组工作线程,通过线程池 ...

  7. [转]为什么阿里巴巴要禁用Executors创建线程池?

    作者:何甜甜在吗 链接:https://juejin.im/post/5dc41c165188257bad4d9e69 来源:掘金 看阿里巴巴开发手册并发编程这块有一条:线程池不允许使用Executo ...

  8. java核心知识点学习----重点学习线程池ThreadPool

    线程池是多线程学习中需要重点掌握的. 系统启动一个新线程的成本是比较高的,因为它涉及与操作系统交互.在这种情形下,使用线程池可以很好的提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考 ...

  9. Android学习笔记之ExecutorService线程池的应用....

    PS:转眼间就开学了...都不知道这个假期到底是怎么过去的.... 学习内容: ExecutorService线程池的应用... 1.如何创建线程池... 2.调用线程池的方法,获取线程执行完毕后的结 ...

随机推荐

  1. Centos yum的源 设置为阿里云源

    在 阿里巴巴镜像站页面,在centos 操作的帮助,有介绍 wget和curl 2种方式来下载CentOS-Base.repo 备份 mv /etc/yum.repos.d/CentOS-Base.r ...

  2. jvm主要组成部分及其作用

    1.类加载器(Class Loader):加载类文件到内存.Class loader只管加载,只要符合文件结构就加载,至于能否运行,它不负责,那是有Exectution Engine 负责的. 2.执 ...

  3. 配置本机的yum源

    配置本机的yum源 环境:操作系统CentOS6.5 1.挂在安装光盘 [root@CentOS40 ~]# mkdir -p /mnt/cdrom[root@CentOS40 ~]# mount / ...

  4. mongodb增删改查基础语法

    转载:https://blog.csdn.net/u012206617/article/details/91047239 1. use DataBaseName 切换/创建数据库use mydb 2. ...

  5. webpack第一节(4)

    每次修改了代码都需要重新手动打包,这样很麻烦,不符合webpack的初衷,我们查看webpack帮助看看有没有可以自动运行的方法 输入 webpack -help 可以发现有个 --watch方法 它 ...

  6. 浅谈CICD持续集成、持续部署的流程(转)

    Jenkins是一个比较流行的持续集成工具GitLab是存储镜像的镜像仓库由客户端将代码push推送到git仓库,gitlab上配置了一个webHook的东西可以触发Jenkins的构建.进入到Jen ...

  7. Spring 讲解(二 )

    1.Spring 容器加载的3种方式 public class ServiceTest { public static void main(String[] args) { //Spring容器加载有 ...

  8. InnoDB索引存储结构

    原创转载请注明出处:https://www.cnblogs.com/agilestyle/p/11429438.html InnoDB默认创建的主键索引是聚簇索引(Clustered Index),其 ...

  9. UVa 699 The Falling Leaves (树水题)

    Each year, fall in the North Central region is accompanied by the brilliant colors of the leaves on ...

  10. 【Flutter学习】页面跳转之路由及导航

    一,概述 移动应用通常通过成为‘屏幕’或者‘页面’的全屏元素显示其内容,在Flutter中,这些元素统称为路由,它们由导航器Navigator组件管理.导航器管理一组路由Route对象,并提供了管理堆 ...