一 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. Application对象详解

    定义// import javax.servlet.ServletContext;// ServletContext类:用于表示应用程序的上下文// 取得application对象ServletCon ...

  2. iview select下拉框的蜜汁小坑

    前言 最近使用iview的select下拉选择器,遇到一个很神奇的问题:选中下拉框里面的一个值,但是再去点下拉框的时候就只剩刚才选中的数据了.感觉应该是插件把刚才选中的数据当做的搜索条件,所以需要做的 ...

  3. C++11新特性之 Move semantics(移动语义)

    https://blog.csdn.net/wangshubo1989/article/details/49748703 这篇讲到了vector的push_back的两种重载版本,左值版本和右值版本.

  4. eureka学习(二)

    eureka服务端创建好后,现在我们让eureka客户端(也就是服务提供者)注册到eureka上去. 首先加入依赖包: <!--将微服务provider注册到eureka--> <d ...

  5. Android中的ImageView的scaleType属性详解

    ImageView的Scaletype决定了图片在View上显示时的样子,如进行何种比例的缩放,及显示图片的整体还是部分,等等. 设置的方式包括: 1. 在layout xml中定义android:s ...

  6. POJ 1151 线段树+扫描线(计算矩形面积并)

    前一篇博客有了讲解就不再叙述了 #include<cstdio> #include<cstring> #include<cmath> #include<ios ...

  7. CentOS 6.9 安装配置zeromq、jzmq

    官方安装配置链接 安装zeromq 步骤一.准备C语言环境 yum -y install gcc-c++ 步骤二.解压缩zeromq tar -zxf zeromq-4.1.6.tar.gz 步骤三. ...

  8. python中匿名函数lambda如何用

    python中经常用到的一个函数:匿名函数lambda ,什么是匿名函数?匿名函数的意义是什么?匿名函数怎么样用?(疑问三连,what,why,how) 一,什么是匿名函数? python中没有名字的 ...

  9. (转)Android Studio解决unspecified on project app resolves to an APK archive which is not supported

    出现该问题unspecified on project app resolves to an APK archive which is not supported as a compilation d ...

  10. stc单片机笔记

    volatile是一个类型修饰符(type specifier).volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值. 简单地说就是防止编译器对代码进行优 ...