Java学习之线程池
线程池的种类
1、newCachedThreadPool
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
这种类型的线程池特点是:
工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
2、newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
3、newSingleThreadExecutor
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
4、newScheduledThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。
线程池七大参数
1.corePoolSize 线程池核心线程大小:
线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,
除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。
2.maximumPoolSize 线程池最大线程数量
一个任务被提交到线程池以后,首先会找有没有空闲存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会缓存到工作队列(后面会介绍)中,如果工作队列满了,才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。
3.keepAliveTime 空闲线程存活时间
一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定.
4.TimeUnit
空闲线程存活时间单位,keepAliveTime的计量单位
5.workQueue 工作队列
新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。
6.threadFactory 线程工厂
创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等
7.handler 拒绝策略
当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,就会执行拒绝策略。
JDK中提供了四种工作队列:
1、ArrayBlockingQueue
基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。
当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。
如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
2、LinkedBlockingQuene
基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,
当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,
因此使用该工作队列时,参数maxPoolSize其实是不起作用的。
3、SynchronousQuene
一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,
而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
4、PriorityBlockingQueue(关键词:优先级线程池)
具有优先级的无界阻塞队列,优先级通过参数Comparator实现。(Comparator使用)
/** * 无参构造,不知道容量就使用队列默认初始容量:11 */ public PriorityBlockingQueueCopy() { this(DEFAULT_INITIAL_CAPACITY, null); } /** * 指定初始容量的构造方法 */ public PriorityBlockingQueueCopy(int initialCapacity) { this(initialCapacity, null); } /** * 指定初始容量和比较器的构造参数,其中参数comparator是实现'优先级'功能的关键! */ public PriorityBlockingQueueCopy(int initialCapacity, Comparator<? super E> comparator) { if (initialCapacity < 1) throw new IllegalArgumentException(); this.lock = new ReentrantLock(); this.notEmpty = lock.newCondition(); this.comparator = comparator; this.queue = new Object[initialCapacity]; }
JDK中提供了4中拒绝策略:
1、CallerRunsPolicy
该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
public static class CallerRunsPolicy implements RejectedExecutionHandler { public CallerRunsPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { //如果线程池没有shutdown直接运行新线程的run方法 if (!e.isShutdown()) { r.run(); } } }
2、AbortPolicy
该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
public static class AbortPolicy implements RejectedExecutionHandler { public AbortPolicy() { } //不执行新线程并且抛出RejectedExecutionException异常 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); } }
3、DiscardPolicy
该策略下,直接丢弃任务,什么都不做。
public static class DiscardPolicy implements RejectedExecutionHandler { public DiscardPolicy() { } //方法体为空什么都没做 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } }
4、DiscardOldestPolicy
该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列。
public static class DiscardOldestPolicy implements RejectedExecutionHandler { public DiscardOldestPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { //判断线程池是否shutdown if (!e.isShutdown()) { //获取线程池工作队列并抛弃最早的一个任务然后尝试执行新线程 e.getQueue().poll(); e.execute(r); } } }
为什么不能用Excutors自动创建线程池
简单来说就是Executors创建的线程池没有设置最大线程数,容易造成OOM(OutOfMemory , 内存溢出)的问题。
Excute和Submit区别
1、提交任务的类型:
execute和submit都属于线程池的方法,execute只能提交Runnable类型的任务。
submit既能提交Runnable类型任务也能提交Callable类型任务。
2、异常:
execute会直接抛出任务执行时的异常,可以用try、catch来捕获,和普通线程的处理方式完全一致。
submit会吃掉异常,可通过Future的get方法将任务执行时的异常重新抛出。
3、返回值:
execute()没有返回值。
submit有返回值,所以需要返回值的时候必须使用submit。
Excute方法代码:
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (!isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); }
Submit方法代码:
//Runnable public <T> Future<T> submit(Runnable task, T result) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task, result); execute(ftask); return ftask; } //Callable public <T> Future<T> submit(Callable<T> task) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task); execute(ftask); return ftask; }