public interface Executor {
void execute(Runnable command);
}
Executor接口中之定义了一个方法execute(Runnable command),该方法接收一个Runable实例,它用来执行一个任务,任务即一个实现了Runnable接口的类。 在Java 5之后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,但是Runnable任务没有返回值,而Callable任务有返回值。并且Callable的call()方法只能通过ExecutorService的submit(Callable
public interface ExecutorService extends Executor {
void shutdown();
List shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
Future submit(Callable task);
Future submit(Runnable task, T result);
Future> submit(Runnable task);
List invokeAll(Collection extends Callable> tasks) throws InterruptedException;
List invokeAll(Collection extends Callable> tasks, long timeout, TimeUnit unit) throws InterruptedException;
T invokeAny(Collection extends Callable> tasks) throws InterruptedException, ExecutionException;
T invokeAny(Collection extends Callable> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
一共12个方法,其中一部分是和执行器生命周期相关的方法,而另一部分则是以各种方式提交要执行的任务的方法。像submit()就是提交任务的一个方法,在实现中做了适配的工作,无论参数是Runnable还是Callable,执行器都会正确执行。当然,这实际上用到的是前文提过的RunnableFuture的实现类FutureTask。
ExecutorService接口继承自Executor接口,它提供了更丰富的实现多线程的方法,比如,ExecutorService提供了关闭自己的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。可以调用ExecutorService的shutdown()方法来平滑地关闭 ExecutorService,调用该方法后,将导致ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。因此我们一般用该接口来实现和管理多线程。
ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown()方法时,便进入关闭状态,此时意味着ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当素有已经提交了的任务执行完后,便到达终止状态。如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可。
那为什么要使用ExecutorService呢?
a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
相比new Thread,Java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
这个类是一个抽象类,规范了所有 工厂(Executors)构造的实例,也就是所有工厂构建的ExecutorService实例必须继承自AbstractExecutorService。这个类是ExecutorService的一个抽象实现。其中,提交任务的各类方法已经给出了十分完整的实现。之所以抽象,是因为和执行器本身生命周期相关的方法,在此类中并未给出任何实现,需要子类扩展完善。
用于执行定时任务
Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。这些方法返回的ExecutorService对象最终都是由ThreadPoolExecutor实现的,根据不同的需求以不同的参数配置,或经过其它类包装。
// 创建固定数目线程的线程池。
public static ExecutorService newFixedThreadPool(int nThreads)
// 创建一个可缓存的线程池,调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线 程并添加到池中。终止并从缓存中移除那些已 有 60 秒钟未被使用的线程。
public static ExecutorService newCachedThreadPool()
// 创建一个单线程化的Executor。
public static ExecutorService newSingleThreadExecutor()
// 创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
newCachedThreadPool() |
-缓存型池子,先查看池中有没有以前建立的线程,如果有,就 reuse.如果没有,就建一个新的线程加入池中 |
newFixedThreadPool(int) |
-newFixedThreadPool与cacheThreadPool差不多,也是能reuse就用,但不能随时建新的线程 |
newScheduledThreadPool(int) |
-调度型线程池 |
newSingleThreadExecutor() |
-单例线程,任意时间池中只能有一个线程 |
一般来说,CachedTheadPool在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的Executor的首选,只有当这种方式会引发问题时(比如需要大量长时间面向连接的线程时),才需要考虑用FixedThreadPool
public interface CompletionService {
Future submit(Callable task);
Future submit(Runnable task, V result);
Future take() throws InterruptedException;
Future poll();
Future poll(long timeout, TimeUnit unit) throws InterruptedException;
}
这个接口是为了方便多个任务执行时,可以方便得获取到执行任务的Future结果。同样,也是五个方法,分为两大方面。一个是对Callable和Runnable类型参数的任务提交,另一方面则是尝试对结果以不同的方式进行获取,take()方法一般是阻塞式的获取,后两者则更灵活。
ExecutorCompletionService这个实现类主要做的就是将执行完成的任务结果放到阻塞队列中,这样等待结果的线程,如执行take()方法会得到结果并恢复执行。
ExecutorCompletionService有3个属性:
AbstractExecutorService类的对象aes
Executor类的对象executor
BlockingQueue
通常,如果executor是AbstractExecutorService的一个实现,则将其赋值给aes属性,否则赋值为null。
在这个类中,executor负责执行任务,而aes则负责做适配处理,返回包装好任务的FutureTask对象。
这里面有一个对于实现功能很重要的内部类QueueingFuture,实现如下:
private class QueueingFuture extends FutureTask {
QueueingFuture(RunnableFuture task) {
super(task, null);
this.task = task;
}
protected void done() { completionQueue.add(task); }
private final Future task;
}
主要是扩展了FutureTask的done方法,将执行结果放入BlockingQueue中
很多场景一个任务完成执行,我们是需要知道它的结果的。为了弥补这个不足, 从JDK1.5开始,在java.util.concurrent包中就有了Callable这个接口。
public interface Callable {
V call() throws Exception;
}
注意到,其中call()方法除了有返回结果以外,比起run()还有异常抛出,这个使用时是要注意的。
在JavaSE5之后,执行器Executor是JDK提供给我们的良好工具,在ExecutorService中也有了支持Callable的submit()方法,那么对于其call()的执行结果我们如何来取呢,这就要提到另一个类——java.util.concurrent.Future。
public interface Future {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
FutureTask
FutureTask是Future具体的实现类
public interface RunnableFuture extends Runnable, Future {
void run();
}
RunnableFuture把Runnable和Future两个接口捏到一起了。实际上,这个接口用意应该是这样的,将需要run()的任务和结果结合在一起,执行了run()能够保证结果被设置从而可以获取到。
用分离锁实现多个线程间的更深层次的共享访问。
用 HashEntery 对象的不变性来降低执行读操作的线程在遍历链表期间对加锁的需求。
通过对同一个Volatile 变量的写/读访问,协调不同线程间读/写操作的内存可见性。
ArrayBlockingQueue
基于数组实现,一个Lock控制互斥访问,两个condition
DelayQueue
延时队列
LinkedBlockingQueue
基于链表实现,两个锁实现
PriorityBlockingQueue
优先级队列
SynchronousQueue
同步队列
BlockingDeque & LinkedBlockingDeque
Semaphore控制同时访问资源的线程个数,如三台打印机,则只允许三个线程从job队列中取出job并执行
例如,会议室人到齐后开始会议。Conference持有一个CountDownLatch对象,设置了初始值,每个Participant持有Conference对象,当运行Participant的run方法时,调用Conference的arrive方法,从而将Conference的CountDownLatch执行countDown操作。在Conference的run方法中,执行CountDownLatch的await方法。
表示请大家等待,等所有集合都准备好了,那么就开始运行,这个过程可以循环。比如:公司部门的周末准备一起出去游玩,先等到所有的人到达汽车才开始启动车辆到目的地去,到后自由玩,然后到1点在一起吃饭。
将CyclicBarrier对象传给所有的Mapper,等所有的MapperTask执行完,将执行结果放入结果集后,调用barrier.await()方法。这时,线程会等待所有的MapperTask执行完。之后会自动调用传入CyclicBarrier的一个Runnable对象,并执行其run方法。
用于两个线程在运行时数据交换
Pattern pattern = Pattern.compile("(A*)*A");
String content = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB";
long current = System.currentTimeMillis();
Matcher matcher = pattern.matcher(content);
if(matcher.matches()) {
System.out.println("Matched");
} else {
System.out.println("Not Matched");
}
System.out.println(System.currentTimeMillis()-current);
对于上述问题,我们可以将content转换成一个字符序列CharSequence,在正则表达式匹配的时候,会隐式调用charAt()方法。因此,可以重写/覆盖charAt()方法,即每次调用的时候,加上一个标识判断,如果标识为false,则退出。另一方面,可以定义一个Timer,当达到了指定delay时间时,改变标识为false,就可以顺利退出match。具体实现如下:
public class MySequence implements CharSequence {
private final CharSequence content;
private final AtomicBoolean signal;
public MySequence(CharSequence content, AtomicBoolean signal) {
this.content = content;
this.signal = signal;
}
@Override
public int length() {
return content.length();
}
@Override
public char charAt(int index) {
if(signal.get())
throw new RuntimeException(new MyException("regex hang"));
return content.charAt(index);
}
@Override
public CharSequence subSequence(int start, int end) {
return new TimeoutRegexCharSequence(content.subSequence(start, end), signal);
}
@Override
public String toString() {
return content.toString();
}
}
public class MyTimerTask extends TimerTask {
private volatile AtomicBoolean signal;
public MyTimerTask(AtomicBoolean signal) {
this.signal = signal;
}
@Override
public void run() {
signal.set(true);
}
}
Pattern pattern = Pattern.compile("(A*)*A");
String content = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB";
Timer timer = new Timer("name", true);
final AtomicBoolean signal = new AtomicBoolean(false);
MyTimerTask timerTask = new MyTimerTask(signal);
timer.schedule(timerTask, 10*1000);
MySequence timeoutInput = new MySequence(content, signal);
Matcher matcher;
try {
matcher = pattern.matcher(timeoutInput);
boolean isMatched = matcher.matches();
} catch(RuntimeException ex) {
Throwable th = ex.getCause();
if(th instanceof MyException) {
System.out.println("regex hang");
}
throw ex;
} finally {
timerTask.cancel();
signal.set(false);
}