线程池-自定义线程池
最近在研究Executors线程池,出了常用的4个基本创建线程池的方法,newFixedThreadPool(),newSingleThreadExextor(),newCachedThreadPool(),newScheduledThreadPool()之外,我们完全可以通过自定义的方式实现自己想要的线程池,进而满足我们项目需求。
对于自定义线程池也是基于ThreadPoolExecutor的构造函数来设置自定义的。先对其构造函数有一个基本了解
/**
*
* @param corePoolSize 池中所保存的核心线程数
* @param maximumPoolSize 池中允许的最大线程数
* @param keepAliveTime 非核心线程空闲等待新任务的最长时间,超过此时间,线程则会被回收
* @param unit 参数时间单位
* @param workQueue 任务队列
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
直接通过创建的方式就可以自定义一个我们想要的线程,自定义线程分为两种,种一:有界队列;种二:无界队列,今日我们通过有界队列对自定义线程有一个深入的了解。
何为有界队列?
- 在使用有界队列时,若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程,若大于corePoolSize,则会将任务加入队列。若队列已满,则在总线程数不大于maximumPoolSize的前提下,创建新的线程,若线程数大于maximumPoolSize,则执行拒绝策略,或其他自定义方式。
通过创建一个简单的实体类和一个测试方法来实现。
实体类:
public class MyTask implements Runnable{
private int taskId;
private String taskName;
//构造函数
public MyTask(int taskId,String taskName){
this.taskId=taskId;
this.taskName=taskName;
}
@Override
public void run() {
System.out.println("run taskId="+this.taskId);
try {
//每次调用线程都沉睡5秒
Thread.sleep(5*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public String toString(){
return Integer.toString(this.taskId);
}
}
测试类:我们通过创建ThreadPoolExecutor来控制线程数,指定想要的队列。
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1, //coreSize
2, //MaxSize
60, //60
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(3) //指定一种队列(有界队列)
);
MyTask mt1 = new MyTask(1, "任务1");
MyTask mt2 = new MyTask(2, "任务2");
MyTask mt3 = new MyTask(3, "任务3");
MyTask mt4 = new MyTask(4, "任务4");
MyTask mt5 = new MyTask(5, "任务5");
MyTask mt6 = new MyTask(6, "任务6");
pool.execute(mt1);
pool.execute(mt2);
pool.execute(mt3);
pool.execute(mt4);
pool.execute(mt5);
pool.execute(mt6);
pool.shutdown();
}
从ThreadPoolExecutor创建的线程条件我们可以得知,我们创建了一个核心线程数,最大线程数为2,非核心线程等待的时间为60秒。
- 只执行mt1,则符合核心线程数,所以会直接执行。因为设置的等待时间为60秒,意思就是假设执行mt1用了10秒,假如在剩下的50秒钟无任何线程进入,则该线程池直接被回收。若进入则直接执行下一个线程。
- 执行mt1,mt2则是核心线程和最大线程,则会在执行完mt1之后,大概5秒之后,执行mt2;
- ArrayBlockingQueue队列是可以容纳3个值的,所以为mt2,mt3,mt4都可以放入队列中,等待执行。执行4个线程中间的时间间隔则为设置的5秒左右。
- 而mt5则已经超过了队列的容纳范围,所以重新创建一个新的线程,因为如需放入队列中,所以应该是和mt1同步的执行,无需等待。
- 而mt6则直接拒绝。因为超过了,最大线程和队列存入个数。最后执行的效果如下:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task 6 rejected from java.util.concurrent.ThreadPoolExecutor@165e6c89[Running, pool size = 2, active threads = 2, queued tasks = 3, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2048)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:821)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1372)
at Thread.Executors.UseThreadPoolExecutor1.main(UseThreadPoolExecutor1.java:42)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
run taskId=1
run taskId=5
run taskId=2
run taskId=3
run taskId=4
通过错误数字都可以看到问题的所在。对于在项目中的使用情况,需要根绝具体需求而定。通过学习各种线程,线程池,有一个整体的规划,这样在使用时方能想到,熟能生巧。