一、显示地创建线程
Java中显示地创建一个线程,只需要创建Thread(或者其子类,或者实现了Runnable的类)的实例即可。通过调用start()方法就可以启动该线程。
例子:
Thread thread = new Thread(task){
@Override
public void run() {
// Blablabal
}
};
thread.start();
当在主函数中执行上述代码片段的时候,该任务将被从主线程中分离出来,在它自己的线程中去执行。但是如果我们简简单单的为每一个任务创建一个线程,那么在任务过多的时候将会出现问题:
1. 消耗资源:活跃的线程将消耗系统资源,尤其是内存;
2. 额外的开销:线程的创建,切换与销毁都是需要开销的;
3. 稳定性:线程数是有个上限的,超过它可能会导致OutOfMemoryError,而且这个上限又是与平台相关的。
二、Executor接口
Executor本身是一个接口,它的定义非常简单,如下:
public interface Executor {
void executor(Runnable command);
}
然而Executor是非常灵活的,因为它把任务的提交与执行解耦开来,从而使系统的线程数可控,而并非由任务数决定。例子:
public class DemoExecutor implements Executor {
@Override
public void execute(Runnable command) {
new Thread(command).start();
}
}
Executor e = new DemoExecutor();
Runnable task = new Runnable() {
@Override
public void run() {
// Blablabla
}
};
e.execute(task);
Executor实际上是一种生产者—消费者模式,提交任务相当于生产者,而执行任务是消费者。所以说Executor很适合在生产消费模式的系统里面使用。要注意到的是,把任务的提交和执行分离开来,使我们能够更加轻松地实现一些机制和策略,例如说我们可以在executor中定义是否执行某个线程等等。
三、管理Executor生命周期:ExecutorService
ExecutorService扩展了Executor接口,为其提供了管理生命周期的方法(也有一些便于任务提交的方法):
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
}
三种状态:关闭,终止,运行。
shutdown是一种平缓的关闭Executor的方式——不接受新的任务,完成已经提交的任务shutdownNow是比较暴力的,它会试图强制关闭所有执行中的任务,同时当然也不会再接受新任务
关闭后的接受的任务将会由Reject Execution Handler来处理。
awaitTermination阻塞直到所有任务完成,响应中断,可以设置timeout。当awaitTermination之后,通常会shutdown,以关闭Executor。
ExecutorService的例子:
public class DemoExecutorService {
private final ExecutorService exec = Executors.newSingleThreadExecutor();
private void handler() {
// Blablabla
}
public void run() {
while (!exec.isShutdown()) {
try {
exec.execute(new Runnable() {
@Override
public void run() {
handler();
}
});
}catch (RejectedExecutionException e) {
// Handling exception
}
}
}
}
四、一些线程池
线程池维护了一定数量的可重用的线程。我们可以想象它的好处:1. 线程池的规模是可控的,防止线程数量过多的情况;2. 线程是可复用的,减少了创建销和毁线程的开销。
Executors类的静态工厂方法newXXThreadPool提供了创建和配置以下几种线程池的方法:
1. newFixedThreadPool
固定大小的线程池,每当提交一个任务时就创建一个,直到线程数量达到最大值。如果有异常结束的线程,会有新的线程被补充进来。
2. newCachedThreadPool
有弹性的线程池,没有线程数量的限制,会根据具体环境回收和创建线程。
3. newSingleThreadExecutor
单个线程的线程池,如果异常结束会有新的线程替代。
4. newScheduledThreadPool
固定大小的线程池。Scheduled表明这是一个定时的(或者延迟的)执行任务的线程池。
五、Future & Callable
由于有些运算是很耗时间的,而有的时候我们并不一定要第一时间拿到结果,那么就可以通过Future和Callable这两个接口来实现。
public interface Callable<V> {
V call() throws Exception;
}
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException,CancellationException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, CancellationException;
}
Callable和Runnable很类似,Callable的优点在于 1. 可以有返回值;2. 可以抛出异常。
Future表明任务的生命周期,其核心在于get方法。get会阻塞直到任务完成,或者抛出异常。Future的获取方式很多,其中之一是通过ExecutorService的submit方法提交一个Callable。
例子:
ExecutorService exec = Executors.newSingleThreadExecutor();
Future<void> future = exec.submit(new Callable<void> { public void call() { // Blablabla } });
try {
future.get();
} catch (InterruptedException e) {
// Blablabla
} catch (CancellationException e) {
//Blablabla
} catch (ExecutionException e) {
//Blablabla
}
另外提一下FutureTask,它实现了Future和Runnable两个接口,因此既可以作为Future又可以被Executor执行。
六、总结
Executor框架把提交与执行解耦,使我们可以更加容易地实现灵活的执行机制和线程管理。