Executor 框架是 Java 5 中引入的,其内部使用了线程池机制,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。通过 Executor 来启动线程比使用 Thread 的 start 方法更好。首先是能够降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。其次能够提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。最后还能提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
Executor 接口中之定义了一个方法 execute(Runnable command),该方法接收一个 Runable 实例,它用来执行一个任务,任务即一个实现了 Runnable 接口的类。ExecutorService 接口继承自 Executor 接口,它提供了更丰富的实现多线程的方法。
ExecutorService 的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了 shutdown()方法时,便进入关闭状态,此时意味着 ExecutorService 不再接受新的任务,但它还在执行已经提交了的任务,当素有已经提交了的任务执行完后,便到达终止状态。
Executors 提供了一系列工厂方法用于创先线程池,返回的线程池都实现了 ExecutorService 接口,我们可以使用它来创建ExecutorService实例。
提供的方法主要有以下几个:
public static ExecutorService newCachedThreadPool()
创建一个可缓存的线程池。调用execute时将重用以前构造的可用的线程。如果现有线程没有可用的,则创建一个新线程并添加到池中。在缓存中如果有60s都未被使用的线程将会被移除,它最多可以Integer.MAX_VALUE个线程。
public static ExecutorService newFixedThreadPool(int nThreads)
创建固定数目线程的线程池。它与newCachedThreadPool差不多,但是它最多只能有nThreads个线程,所以它不能随时创建新的线程,当线程池满时,需要创建新的线程时会在队列中等待,知道有线程执行完成并且从池中移除。
public static ExecutorService newSingleThreadExecutor()
创建一个单线程化的Executor,其在任何时候线程池中都只有一个线程,执行完之后会移除。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
Executor 运行 Runnable 任务
在获取上述的几个实例后,我们就可以调用实例的execute方法运行runnable方法。
示例:
1 | ExecutorService executor= Executors.newCachedThreadPool(); |
Executor 运行 Callable 任务
在使用线程的过程中,我们经常会遇到由于没有返回值而带来的问题。而在使用ExecutorService的时候,我们就可以运行Callable接口来获得返回值。Callable 的 call()方法可以通过 ExecutorService 的 submit(Callable task) 方法来执行,并且返回一个 Future,同样,将 Runnable 的对象传递给 ExecutorService 的 submit 方法,则该 run 方法自动在一个线程上执行,并且会返回执行结果 Future 对象,但是在该 Future 对象上调用 get 方法,将返回 null。
示例:
1 | Callable<String>s=new Callable<String>() { |
自定义线程池
除了使用自带的线程池,我们也可以自定义线程池来执行任务。自定义线程池主要是使用ThreadPoolExecutor类创建。其构造方法如下:
public ThreadPoolExecutor (int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue)
当中参数的含义如下:
corePoolSize
线程池中的最少线程数,当添加新任务时,当池中的线程少于corePoolSize,即使有空闲线程,也会新建线程添加到池中。
maximumPoolSize
线程池中最大的线程数。
keepAliveTime
空闲线程的保持时间
unit
时间单位(秒/分…)
workQueue
任务执行前保存任务的队列
所以当有新任务要处理时,先看线程数是否大于corePoolSize,再看缓冲队列是否满,最后看线程数是否大于最大maximumPoolSize。
不同缓冲队列的区别
SynchronousQueue:它将任务直接提交给线程处理,当不存在空闲线程时会创建新线程,所以maximumPoolSizes应设置为Integer.MAX_VALUE,已避免提交任务失败,newCachedThreadPool就是采取这种队列。
LinkedBlockingQueue:队列的容量是无限的,所以线程中最大线程数不会超过corePoolSize,newFixedThreadPool就是采用这种队列。
ArrayBlockingQueue:有界队列,队列中的任务数量有限,这样可以防止资源过度消耗。
示例:
1 | ThreadPoolExecutor r=new ThreadPoolExecutor(3,5,50, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(20)); |