一,介绍
类视图如下:
自Java 1.5后,Java对线程相关的库做了很大的拓展,线程池就是其中之一。Java线程的新特性多数在java.util.concurrent,其包含众多的接口和类。其中java.util.concurrent.Executor是这些类的最顶级接口。其是执行线程的一个工具。ExecutorService在Executor的基础上增加了一些方法,以向线程池提交任务。包括实现Runnable接口的任务和实现Callable接口的任务。两个方法为:
Future<?> submit(Runnable task)
Future<T> submit(Callable<T> task)
实现Runnable接口的任务,其run方法的返回值为void,因此submit的返回值为Future<?>类型,?表示匹配任意一种类型。实现Callable<T>接口的任务,其call方法的返回值类型为T,对应submit的返回值类型则是Future<T>。具有返回值的Callable的任务对于多个线程中传递状态和结果是非常有用的。另外使用这两个方法返回的Future对象可以阻塞当前线程直到提交的任务运行完毕以获取结果,也可以用以取消任务的执行,或者检测任务是否被取消或者是否执行完毕。如果不使用Future,我们检测一个线程是否执行完毕通常使用Thread.join()或者对状态标识进行轮询。
阻塞等待结果,或者在某个时候获取结果的方法是,调用Futrue的get()方法。这样调用线程便会等待在线程执行完成并将结果反馈到Futrue对象。之后调用线程可以对处理结果进行判别处理。isDone(),isCancelled()可以用以判断当前的任务执行状态(注意被打断或取消也是Done的一种)。cancel(Boolean)方法则可以用以取消线程执行。如果参数是true表示即使任务已经执行也将试图取消它,如果是false则表示如果任务没有被执行则取消,执行了则不取消执行。
可以使用Executor的静态方法创建实现ExecutorService的线程池类。对应方法创建的线程池描述如下:
1,newSingleThreadExecutor,单线程线程池。单个线程执行器,内有一个线程执行Runnable的任务。如果该线程发生异常终了,则创建新的进行补充。可以保证任务顺 序的执行。
2,newFixedThreadPool,创建固定线程数目(参数指定)的线程池。每加入一个任务,创建一个线程,直到达到固定数目后,将会有固定大小的线程执行Runnable的任务。如果有线程异常终了,则创建新的线程来进行补充。
3,newCachedThreadPool, 缓冲线程池,产生一个大小可变的线程池。当线程池的线程多于执行任务所需要的线程的时候,对空闲线程(即60s没有任务执行)进行回收;当执行任务的线程数不足的时候,自动拓展线程数量。因此线程数量是JVM可创建线程的最大数目。
4,newSingleThreadScheduledExecutor,单个线程调度执行器,产生单个线程执行任务,采用schedule方法可以延期或者定期的执行任务
5,newScheduledThreadPool,调度线程执行器,当有任务的时候,创建线程,直到默认线程数量(参数指定),当执行任务所需的线程多于该数目的时候,自动拓展线程数量,没有上限;如果线程空闲则被回收,直到默认线程数量。
6,另外可以自定义线程池,请参见使用代码例子。
另外注意CompletionService接口,其能够根据任务的执行先后顺序得到执行结果。
二,使用例子
//两种任务
package poolmanager;
public class RunnableTask implements Runnable {
int i = 0;
public RunnableTask(int i) {
this.i = i;
}
public void run() {
System.out.println("******************");
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task is :" + i);
}
}
package poolmanager;
import java.util.concurrent.Callable;
public class CallableTask implements Callable<String> {
public String ok = "OK!";
public String ng = "Error Happened in Running!";
int i = 0;
public CallableTask(int i){
this.i = i;
}
public String call(){
System.out.println("******************");
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task is :" + i);
return ok;
}
}
//线程管理类
package poolmanager;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class JavaThreadPoolManager {
int ThreadSize = 2;
int ThreadMaxSize = 2*ThreadSize;
ExecutorService executorSingleThreadExecutor = null;
ExecutorService executorFixedThreadPool = null;
ExecutorService executorCachedThreadPool = null;
ScheduledExecutorService executorSingleThreadScheduledExcutor = null;
ScheduledExecutorService executorScheduledThreadPool = null;
//自定义线程池
BlockingQueue<Runnable> bqueue = null;
ThreadPoolExecutor pool = null;
public void startJavaThreadPoolManager(){
//单个线程执行器,内有一个线程执行Runnable的任务。
//如果该线程发生异常终了,则创建新的进行补充。
//可以保证任务顺序的执行。
executorSingleThreadExecutor = Executors.newSingleThreadExecutor();
//固定数目的线程执行器。
//每加入一个任务,创建一个线程,直到达到固定数目后,
//将会有固定大小的线程执行Runnable的任务。
//如果有线程异常终了,则创建新的线程来进行补充。
executorFixedThreadPool = Executors.newFixedThreadPool(ThreadSize);
//缓冲线程执行器,产生一个大小可变的线程池。
//当线程池的线程多于执行任务所需要的线程的时候,
//对空闲线程(即60s没有任务执行)进行回收;
//当执行任务的线程数不足的时候,自动拓展线程数量。因此线程数量是JVM
//可创建线程的最大数目。
executorCachedThreadPool = Executors.newCachedThreadPool();
//单个线程调度执行器,产生单个线程执行任务,采用schedule方法可以延期或者定期的执行任务
executorSingleThreadScheduledExcutor = Executors
.newSingleThreadScheduledExecutor();
//调度线程执行器,当有任务的时候,创建线程,直到默认线程数量(参数指定),
//当执行任务所需的线程多于该数目的时候,
//自动拓展线程数量,没有上限;
//如果线程空闲则被回收,直到默认线程数量。
executorScheduledThreadPool = Executors.newScheduledThreadPool(ThreadSize);
//自定义线程池
bqueue = new ArrayBlockingQueue<Runnable>(20);
//第三个参数是线程池线程所允许的空闲时间
//第四个参数是线程池线程空闲时间的单位
//第五个参数是缓冲任务队列
//第六个参数是缓冲区满的时候,对任务的处理策略
//第六个参数有如下几种选择:
//ThreadPoolExecutor.AbortPolicy()
//抛出java.util.concurrent.RejectedExecutionException异常
//ThreadPoolExecutor.CallerRunsPolicy()
//重试添加当前的任务,即自动以任务为参数,再次调用execute()方法
//ThreadPoolExecutor.DiscardOldestPolicy()
//抛弃一个已有的任务(抛弃当前任务队列的头部任务,即最开始加入的任务)
//ThreadPoolExecutor.DiscardPolicy()
//抛弃当前这个任务
pool = new ThreadPoolExecutor(
ThreadSize,ThreadMaxSize,2,TimeUnit.MILLISECONDS,bqueue
,new ThreadPoolExecutor.DiscardOldestPolicy());
}
public void endJavaThreadPoolManager(){
executorSingleThreadExecutor.shutdown();
executorFixedThreadPool.shutdown();
executorCachedThreadPool.shutdown();
executorSingleThreadScheduledExcutor.shutdown();
executorScheduledThreadPool.shutdown();
pool.shutdown();
}
}
//使用
package poolmanager;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class MainThread {
public static void main(String[] args) throws InterruptedException {
JavaThreadPoolManager jThreadPoolManager = new JavaThreadPoolManager();
jThreadPoolManager.startJavaThreadPoolManager();
RunnableTask command1 = new RunnableTask(1);
CallableTask command2 = new CallableTask(2);
jThreadPoolManager.executorSingleThreadExecutor.execute(command1);
jThreadPoolManager.executorSingleThreadExecutor.submit(command2);
jThreadPoolManager.executorFixedThreadPool.execute(command1);
jThreadPoolManager.executorFixedThreadPool.submit(command2);
jThreadPoolManager.executorCachedThreadPool.execute(command1);
jThreadPoolManager.executorCachedThreadPool.submit(command2);
jThreadPoolManager.executorScheduledThreadPool.execute(command1);
jThreadPoolManager.executorScheduledThreadPool.submit(command2);
jThreadPoolManager.executorSingleThreadScheduledExcutor.execute(command1);
jThreadPoolManager.executorSingleThreadScheduledExcutor.submit(command2);
jThreadPoolManager.executorScheduledThreadPool.execute(command1);
jThreadPoolManager.executorScheduledThreadPool.schedule(command1,60, TimeUnit.SECONDS);
jThreadPoolManager.executorScheduledThreadPool.schedule(command2,60, TimeUnit.SECONDS);
jThreadPoolManager.executorSingleThreadScheduledExcutor.scheduleWithFixedDelay(command1, 0, 20, TimeUnit.SECONDS);
Future<String> returnFuture =
jThreadPoolManager.executorSingleThreadScheduledExcutor.submit(command2);
System.out.println(returnFuture.isCancelled());
System.out.println(returnFuture.isDone());
returnFuture.cancel(true);
System.out.println(returnFuture.isCancelled());
System.out.println(returnFuture.isDone());
String resultString =null;
if(!returnFuture.isCancelled()){
try {
resultString = returnFuture.get();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(resultString);
}
jThreadPoolManager.pool.execute(command1);
}
}
三,再讨论
我们看到自定义线程池的时候,可以自定义任务队列,其中有如下几种任务队列的自定义方法:
1,直接提交 选择任务队列为 SynchronousQueue
的,则默认的队列大小为1,在这种情况下,如果没有空闲线程,则往往直接创建新的线程执行该任务,因此往往需要不设立线程的数目上限。该策略可以避免在处理可能具有内部依赖的请求集时出现锁,因为其任务执行顺序必然是先加入的先被执行。
2,无界队列 选择任务队列为LinkedBlockingQueue 其意义即任务队列大小无限制。这样创建的线程往往不超过corePoolSize。因此maximumPoolSize的值就没有什么意义。适合于任务相互独立。
3,有界队列 使用具有最大线程数目限制maximumPoolSize的时候,有界队列例如ArrayBlockingQueue有助于防止资源耗尽,但可能较难控制。
使用大队列小池子有利于降低CPU使用,上下文切换,但是吞吐量不高;如果小队列大池子,则CPU使用率高,但是可能调度开销大,也会降低吞吐量。
另外,选择上述任务队列的方案,需要与corePoolSize和maximumPoolSizes这对参数配合。
因此,线程池的选择要根据任务的类型,比如任务是数量大、单个执行时间短,还是任务数量小、单个执行长,还是任务数量又多,执行时间又长,来进行不同的选择。而自定义线程池也是需要根据任务的类型选择任务队列、corePoolSize和maximumPoolSizes。
此外,keepAliveTime和maximumPoolSize及BlockingQueue的类型均有关系。如果BlockingQueue是无界的,那么永远不会触发maximumPoolSize,自然keepAliveTime也就没有了意义。如果很容易就产生线程的回收,也会导致性能下降。
参考:
1,JDK Document
2, http://www.cnblogs.com/jersey/archive/2011/03/30/2000231.html