最近在研究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, BlockingQueueworkQueue) { 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(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=1run taskId=5run taskId=2run taskId=3run taskId=4
通过错误数字都可以看到问题的所在。对于在项目中的使用情况,需要根绝具体需求而定。通过学习各种线程,线程池,有一个整体的规划,这样在使用时方能想到,熟能生巧。