Executor创建线程池
为了更好的控制多线程,JDK提供了一套线程框架Executor,帮助开发人员有效的控制线程。他们都在java.util.concurrent包中,是JDK并发包的核心。其中有一个比较重要的类;excutors,它扮演者线程工厂的角色,我们可以通过Executor可以创建特定功能的线程池。
Exexutor创建线程池的方法:
1. newFixedThreadPool() : 该方法广返回一个固定数量的线程池,该方法的线程池数量始终不变,当有一个任务提交时,若线程池中空闲,则立即执行,若没有,则会被暂时存放在一个任务队列中,等待有空闲的线程取执行。
源码如图:
2.newSingleThreadPool():创建一个线程的线程池,若空闲则执行,若没有空闲线程则暂缓在任务队列中
源码如图:
3.newCachedThreadPool():该方法返回一个可以根据实际情况调整线程个数的线程池,不限制最大线程数量,只要有任务来,就创建一个线程取执行任务。若无任务则不创建线程,并且每一个空闲线程会在60后自动回收
源码如图:
4.newScheduleThreadPool():该方法返回一个ScheduledExecutorService对象,但该线程池可以指定线程的数量,其中的每一个线程都可以实现定时器的作用。
源码如图:
底层调用:
无论创建那种类型的线程池,最低层还是调用了 下面的ThreadPoolExecutor,不同的线程池传递给ThreadPoolExector的参数不同。
源码如图:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
1.第一个参数: 核心线程数,即线程池初始化时就创建的线程个数;
2.第二个参数:线程池中最大线程数
3.第三个参数:线程池中每一个线程保持活着的空闲时间。
4.第四个参数:时间单位
5.第五个参数:指定一个阻塞队列,若线程池中线程都忙着,再有任务过来,则排队等待
6.第六个参数:线程工厂
7.第七个参数:拒绝执行的handler,具体拒绝执行的逻辑
自定义线程池中对于队列是什么类型的比较关键:
1.有界队列:在使用有界队列时。若有新任务需要执行,如果线程池实际线程数小于核心线程数(corePoolSize),则有优先创建线程。若大于corePoolSize则会将任务加入到队列中,若队列已满,则在总线程数不大于最大线程数(maximumPoolSize)的前提下,创建新的线程,若线程数大于maximumPoolSize,则执行拒绝策略。如对列类型为:arrayBlockQueue。
2.无界队列:与有界队列相比,除非系统资源耗尽,否则无界队列的任务不存在任务入队失败的情况。当有新任务到来时,系统的线程数小于corePoolSize时,则新建线程执行任务,当达到corePoolSize后,就不会继续增加。若获取仍有新的任务加入,而又没有空闲的线程资源,则任务直接进入队列。若任务创建和处理速度差异很大,无界队列会保持快速增长,直到耗尽系统资源。
自定义线程池使用有界队列的例子:
待执行任务:
package com.company.threadPoolExecutorMySelf;
/**
* Created by BaiTianShi on 2018/8/22.
*/
public class MyTask implements Runnable {
private String id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public MyTask(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public void run() {
System.out.println("正在处理id为:"+this.id+"的任务数据");
try {
//模拟处理过程
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public String toString(){
return this.id;
}
}
使用自定义线程池:
package com.company.threadPoolExecutorMySelf;
import com.company.masterWorker.Main;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Created by BaiTianShi on 2018/8/22.
*/
public class ThreadPoolExecutorMySelf {
public static void main(String[] args) {
ThreadPoolExecutor t = new ThreadPoolExecutor(
1,
2,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(3),
new MyRejected()
);
MyTask m1 = new MyTask("1","任务1");
MyTask m2 = new MyTask("2","任务2");
MyTask m3 = new MyTask("3","任务3");
MyTask m4 = new MyTask("4","任务4");
MyTask m5 = new MyTask("5","任务5");
MyTask m6 = new MyTask("6","任务6");
t.execute(m1);
t.execute(m2);
t.execute(m3);
t.execute(m4);
t.execute(m5);
t.execute(m6);
// shutdown和shutdownNow的区别
//1. shutdown 表示当前线程池状态变为SHUTDOWN,不再允许添加元素了,否则会抛出RejectExecutionException异常.
//2.shutdownNow 表示线程池的状态立即变为STOP状态,并试图停止所有正在执行的线程,不再处理还在等待着的任务,并且会立即返回那些没执行的任务
t.shutdown();
}
}
自定义拒绝策略:
package com.company.threadPoolExecutorMySelf;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
/**
* Created by BaiTianShi on 2018/8/22.
*/
public class MyRejected implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("线程被拒绝");
System.out.println(r.toString()+"被拒绝加入队列,将以日志的形式输出");
}
}
运行结果
线程被拒绝
正在处理id为:5的任务数据
正在处理id为:1的任务数据
6被拒绝加入队列,将以日志的形式输出
正在处理id为:2的任务数据
正在处理id为:3的任务数据
正在处理id为:4的任务数据
在上述代码中,通过自定义线程池来执行任务,根据ThreadPoolExecutor使用有界队列特性我们分析一下整个过程。
1.有6个任务准备加入队列,核心线程数为1,所以任务m1直接被执行,
2.除m1外其他5个准备放入队列中,但是有界队列的长度为3,所以只有m2、m3、m4可以入队。
3.接下来只能在maximumPoolSize范围内扩展当前线程数maximumPoolSize是2,所以m1的线程加上执行m5的线程达到最大线程数。
4.m6没有队列可入,也不能被马上执行m6只能被拒绝策略执行,实际应用中我们可以以日志的形式记录等
自定义线程池使用无界队列的例子:
package com.company.threadPoolExecutorMySelf;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by BaiTianShi on 2018/8/22.
*/
public class ThreadPoolExecutorMySelf2 implements Runnable {
private static AtomicInteger count = new AtomicInteger(0);
@Override
public void run() {
int tem = count.incrementAndGet();
System.out.println("任务:"+tem);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue();
ThreadPoolExecutor pool = new ThreadPoolExecutor(
5,
10,
120,
TimeUnit.SECONDS,
queue
);
for(int i=0;i<20;i++){
ThreadPoolExecutorMySelf2 t = new ThreadPoolExecutorMySelf2();
pool.execute(t);
}
try {
Thread.sleep(1000);
System.out.println("队列长度:"+queue.size());
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
任务:1
任务:2
任务:3
任务:4
任务:5
队列长度:15
任务:6
任务:7
任务:8
任务:10
任务:9
任务:11
任务:15
任务:14
任务:13
任务:12
任务:16
任务:20
任务:19
任务:18
任务:17
可见,当达到corePoolSize时,任务会全部进入到无界队列中等待,直到有空闲线程来执行队列中的任务。
JDK拒绝策略:
JDK提供了四种拒绝策略,但是都不是很实用,这里简单介绍一下
AbortPolicy:直接抛出异常阻止系统正常工作
CallerRunPolicy:只要线程池未关闭,改策略直接在调用者线程池中,运行当前被丢弃的任务
DiscardOldPolicy:丢弃最老的一个请求,尝试再次提交当前任务
DiscardPoilicy:丢弃无法处理的任务,不给于任务处理
这里我们一DiscardOldPolicy为例做一个演示:
主线程:
package com.company.threadPoolExecutorMySelf;
import com.company.masterWorker.Main;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Created by BaiTianShi on 2018/8/22.
*/
public class ThreadPoolExecutorMySelf {
public static void main(String[] args) {
ThreadPoolExecutor t = new ThreadPoolExecutor(
1,
2,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(3),
new ThreadPoolExecutor.DiscardOldestPolicy()
);
MyTask m1 = new MyTask("1","任务1");
MyTask m2 = new MyTask("2","任务2");
MyTask m3 = new MyTask("3","任务3");
MyTask m4 = new MyTask("4","任务4");
MyTask m5 = new MyTask("5","任务5");
MyTask m6 = new MyTask("6","任务6");
t.execute(m1);
t.execute(m2);
t.execute(m3);
t.execute(m4);
t.execute(m5);
t.execute(m6);
// shutdown和shutdownNow的区别
//1. shutdown 表示当前线程池状态变为SHUTDOWN,不再允许添加元素了,否则会抛出RejectExecutionException异常.
//2.shutdownNow 表示线程池的状态立即变为STOP状态,并试图停止所有正在执行的线程,不再处理还在等待着的任务,并且会立即返回那些没执行的任务
t.shutdown();
}
}
任务:
package com.company.threadPoolExecutorMySelf;
/**
* Created by BaiTianShi on 2018/8/22.
*/
public class MyTask implements Runnable {
private String id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public MyTask(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public void run() {
System.out.println("正在处理id为:"+this.id+"的任务数据");
try {
//模拟处理过程
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public String toString(){
return this.id;
}
}
运行结果:
正在处理id为:5的任务数据
正在处理id为:1的任务数据
正在处理id为:3的任务数据
正在处理id为:4的任务数据
正在处理id为:6的任务数据
由于使用的有界队列ArrayBlockingQueue,且提交了6个任务,第6个任务到来时,线程池中已有2个线程正在处理任务,且队列已满,且线程池的最大线程数为2。因为拒绝策略使用的是DiscardOldestPolicy,对其oldest的一个任务2进行删除,并尝试提交当前的任务6,这样任务6就进入了队列中。任务2被丢弃。
自定义拒绝策略
参见本篇关于ThreadPoolExecutor使用有界队列ArraLockerdQueue的例子,此里中实现并使用了自定义拒绝策略:MyRejected.java