Java并发编程系列(七)线程池的使用:如果用new Thread().start的方式使用线程,当线程执行完毕后会销毁,如果频繁地创建和销毁线程,对系统的消耗将会非常大。
那么如果线程执行完任务后还可以重新使用,那么就可以提高性能。线程JDK里面线程池主要使用的是ThreadPoolExecutor,现在来初步感受一下线程池的使用:
package com.rancho945.concurrent; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class Test { public static void main(String[] args) { //创建线程池 ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 20, 100, TimeUnit.SECONDS, new LinkedBlockingQueue()); for (int i = 0; i < 8; i++) { //只要实现了Runnable接口的类都可以往里面扔 executor.execute(new WorkTask(i)); } } static class WorkTask implements Runnable{ final private int taskNum; public WorkTask(int num) { taskNum = num; } @Override public void run() { System.out.println("hello world,I am task "+taskNum+"----executed by "+Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
执行结果:
hello world,I am task 1----executed by pool-1-thread-2 hello world,I am task 2----executed by pool-1-thread-3 hello world,I am task 0----executed by pool-1-thread-1 hello world,I am task 3----executed by pool-1-thread-4 hello world,I am task 4----executed by pool-1-thread-5 hello world,I am task 5----executed by pool-1-thread-2 hello world,I am task 6----executed by pool-1-thread-4 hello world,I am task 7----executed by pool-1-thread-1
使用线程池的主要步骤是,先创建线程池,然后把实现了Runnable接口的对象传进execute方法中即可。下面我们来仔细说说有关线程池的那些事。
ThreadPoolExecutor的构造函数有四个:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueworkQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); } public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler); } public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler); } public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
这些构造函数最终都会调用到最后一个构造函数:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueworkQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
我们一个一个来解释一些这些构造函数的含义:
核心池的大小。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。如果在执行任务之前调用了prestartAllCoreThreads()会创建好所有的核心线程,调用prestartCoreThread()方法会创建好一个核心线程。
最大线程数量,包含corePoolSize。当任务超过核心线程池大小的时候,先把任务放在同步队列中,如果同步队列满了,再创建线程,直到线程池大小到达maximumPoolSize为止。
线程存活时间,默认只针对非核心线程。如果一个非核心线程在空闲状态下,到达了keepAliveTime,那么该线程会销毁。如果调用了allowCoreThreadTimeOut(true),那么对核心线程也会起作用,直到线程数为零。
keepAliveTime的时间单位,有以下几种:
1. TimeUnit.DAYS; //天 2. TimeUnit.HOURS; //小时 3. TimeUnit.MINUTES; //分钟 4. TimeUnit.SECONDS; //秒 5. TimeUnit.MILLISECONDS; //毫秒 6. TimeUnit.MICROSECONDS; //微妙 7. TimeUnit.NANOSECONDS; //纳秒
实现了BlockingQueue的阻塞队列,用来存储等待执行的任务,超出核心线程池的任务会放在这个队列当中。阻塞队列主要有以下几种选择:
//基于数组的阻塞队列,创建的时候必须指定大小 1. ArrayBlockingQueue; //基于链表的阻塞队列,可以不指定大小,默认为Integer.MAX_VALUE 2. LinkedBlockingQueue; //同步队列,它是没有容量的,不会保存提交的任务。 //也就是说如果使用这个队列,那么当任务数超出核心线程数后,直接新建非核心线程执行新任务。 3. SynchronousQueue; //有优先级的阻塞队列,如果任务有优先级,那么会根据优先级来取出任务,比如后面加进来的任务优先级比较高,那么会执行后面的任务,而不是哪个先进队就执行哪个。 4. PriorityBlockingQueue
线程工厂,主要是创建线程。默认为Executors中的DefaultThreadFactory,这里暂时不用管,只知道能创建线程即可。也可以自己定义,只需要实现ThreadFactory接口即可。
当任务超出最大线程值+队列最大值的时候,线程池就会把提交的任务拒绝。handler作为拒绝任务的处理策略,有下面几种
//丢弃任务并抛出RejectedExecutionException异常,线程池默认是该策略 1. ThreadPoolExecutor.AbortPolicy。 //什么都不做,默默地丢弃任务,也不抛出异常。 2. ThreadPoolExecutor.DiscardPolicy //丢弃队列最前面的任务,然后重新尝试执行任务(如果还是失败则重复此过程) 3. ThreadPoolExecutor.DiscardOldestPolicy //由调用线程处理该任务,比如说在主线程中调用线程池的execute方法被拒绝,该任务在主线程中执行,相当于在主线程中直接执行run方法。 4. ThreadPoolExecutor.CallerRunsPolicy
Executors(注意多了个s)类提供了几个静态方法创建线程池,一般情况下用静态方法创建线程更加方便一些:
//创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE Executors.newCachedThreadPool(); //创建容量为1的缓冲池(单线程线程池) Executors.newSingleThreadExecutor(); //创建固定容量大小的缓冲池 Executors.newFixedThreadPool(int);
如果理解了前面的,那么看看这几个静态方法便一目了然。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); } public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue ())); } public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue ()); }
线程池的关闭有两个方法:
//调用该方法后不会接受新的任务,等待队列中的任务执行完成后关闭线程池 public void shutdown(); //该方法尝试中断正在执行的任务,中断的方式是调用线程的interrupt方法,并且只调用一次,如果线程再次阻塞,则不能保证任务能够完全执行完。队列中没有执行完的任务以list的形式返回。 public ListshutdownNow();
另外线程池还有submit、invokeAll、invokeAny用于执行有返回结果以及批量执行有返回结果的任务。这里涉及到Future等接口,这里暂时不说。