Threads and Runnables
Java自JDK1.0就支持线程,一般来说,在启动线程之前,我们需要指定线程实际执行方法体,通常我们可以通过重写Runnable接口中的run方法来完成。
Runnable task = () -> {
String threadName = Thread.currentThread().getName();
System.out.println("Hello " + threadName);
};
task.run();
Thread thread = new Thread(task);
thread.start();
System.out.println("Done!");
注意,Runnable
是一个函数式接口(接口中有且只有一个抽象方法),所以我们可以使用Java 8中的lambda表达式来打印当前线程名称。执行结果如下:
Hello main
Hello Thread-0
Done!
或者
Hello main
Done!
Hello Thread-0
在多线程中,子程序与主程序执行顺序是不确定的,这就使得多线程编程在更为复杂的应用程序中的应用成为挑战。
在进行更深入的探讨前,我们先作一个简单的说明,这里我们使用线程休眠的方式来模拟后续章节中线程长时间执行的业务场景。
Runnable runnable = () -> {
try {
String name = Thread.currentThread().getName();
System.out.println("Foo " + name);
TimeUnit.SECONDS.sleep(1);
System.out.println("Bar " + name);
}
catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(runnable);
thread.start();
在JDK 1.5之前,多线程编程繁琐易错,JDK 1.5之后,引入了Concurrency API
并发编程框架,框架包含了许多有用用以处理多线程编程的Java类。
Executors
public interface ExecutorService extends Executor
Executors
以内部线程池的方式支持异步任务的调度。
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
String threadName = Thread.currentThread().getName();
System.out.println("Hello " + threadName);
});
注意:如果想要停止Executors
服务,可以调用shutdown()
或者shutdownNow()
方法。
try {
System.out.println("attempt to shutdown executor");
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
System.err.println("tasks interrupted");
}
finally {
if (!executor.isTerminated()) {
System.err.println("cancel non-finished tasks");
}
executor.shutdownNow();
System.out.println("shutdown finished");
}
Callables and Futures
Executors
除支持Runnable
接口类型的任务外,还支持Callable
接口类型的任务,与Runnable
接口类似,Callable
也是函数式接口,区别是Callable
有返回值,而Callable
是没有返回值的。
Callable<Integer> task = () -> {
try {
TimeUnit.SECONDS.sleep(1);
return 123;
}
catch (InterruptedException e) {
throw new IllegalStateException("task interrupted", e);
}
};
当我们调用ExecutorService
的submit
方法时,对得到一个Future
对象,通过这个对象,我们就可以获得Callable
类型任务实际执行结果。
Callable<Integer> task = () -> {
try {
TimeUnit.SECONDS.sleep(1);
return 123;
}
catch (InterruptedException e) {
throw new IllegalStateException("task interrupted", e);
}
};
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<Integer> future = executor.submit(task);
System.out.println("future done? " + future.isDone());
Integer result = future.get();
System.out.println("future done? " + future.isDone());
System.out.print("result: " + result);
调用Future
对象的get
方法会阻塞当前线程直至callable对象执行完毕,并返回执行结果。
future done? false
future done? true
result: 123
Timeouts
如前所述,调用Future
对象的get
方法会阻塞当前线程。考虑最坏的情况,Callable
对象一直无法退出,这将会导致应用程序本身无响应。所以,为了处理这种异常,我们可以传递一个时间参数。
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<Integer> future = executor.submit(() -> {
try {
TimeUnit.SECONDS.sleep(2);
return 123;
}
catch (InterruptedException e) {
throw new IllegalStateException("task interrupted", e);
}
});
future.get(1, TimeUnit.SECONDS);
当超时时间到,自动抛出TimeoutException
异常。
Exception in thread "main" java.util.concurrent.TimeoutException
at java.util.concurrent.FutureTask.get(FutureTask.java:205)
InvokeAll
阻塞进程,直至返回所有的任务执行结果
ExecutorService executor = Executors.newWorkStealingPool();
List<Callable<String>> callables = Arrays.asList(
() -> "task1",
() -> "task2",
() -> "task3");
executor.invokeAll(callables)
.stream()
.map(future -> {
try {
return future.get();
}
catch (Exception e) {
throw new IllegalStateException(e);
}
})
.forEach(System.out::println);
InvokeAny
阻塞进程,直至返回第一个执行完毕的任务结果
Callable<String> callable(String result, long sleepSeconds) {
return () -> {
TimeUnit.SECONDS.sleep(sleepSeconds);
return result;
};
}
ExecutorService executor = Executors.newWorkStealingPool();
List<Callable<String>> callables = Arrays.asList(
callable("task1", 2),
callable("task2", 1),
callable("task3", 3));
String result = executor.invokeAny(callables);
System.out.println(result);
// => task2
上述例子使用了Java 8 引入的新的工厂方法newWorkStealingPool()
,newWorkStealingPool()
返回的是ForkJoinPool
,与正常的Executor略有不通,ForkJoinPool
会根据主机CPU返回来创建包含给定数目线程的线程池。
Scheduled Executors
ScheduledExecutorService
定期任务执行。
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("Scheduling: " + System.nanoTime());
ScheduledFuture<?> future = executor.schedule(task, 3, TimeUnit.SECONDS);
TimeUnit.MILLISECONDS.sleep(1337);
long remainingDelay = future.getDelay(TimeUnit.MILLISECONDS);
System.out.printf("Remaining Delay: %sms", remainingDelay);
schedule
返回的是ScheduledFuture
对象,除schedule
方法外,ScheduledExecutorService
还提供另外两种方法:
scheduleAtFixedRate()
scheduleWithFixedDelay()
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("Scheduling: " + System.nanoTime());
int initialDelay = 0;
int period = 1;
executor.scheduleAtFixedRate(task, initialDelay, period, TimeUnit.SECONDS);
注意:scheduleAtFixedRate()
方法并不考虑实际任务持续时间,比如,如果任务本身执行持续时间需要2秒,但你却为scheduleAtFixedRate()
执行每1秒执行一次,那么线程池中的线程将很快用完,这种情况下,你可以考虑使用scheduleWithFixedDelay()
方法来替代。
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable task = () -> {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("Scheduling: " + System.nanoTime());
}
catch (InterruptedException e) {
System.err.println("task interrupted");
}
};
executor.scheduleWithFixedDelay(task, 0, 1, TimeUnit.SECONDS);
https://winterbe.com/posts/2015/04/07/java8-concurrency-tutorial-thread-executor-examples/