CompletableFuture是对Future的扩展和增强。CompletableFuture实现了Future接口,并在此基础上进行了丰富的扩展,完美弥补了Future的局限性,同时CompletableFuture实现了对任务编排的能力。借助这项能力,可以轻松地组织不同任务的运行顺序、规则以及方式。从某种程度上说,这项能力是它的核心能力。而在以往,虽然通过CountDownLatch等工具类也可以实现任务的编排,但需要复杂的逻辑处理,不仅耗费精力且难以维护。
CompletableFuture的继承结构如下:
CompletionStage接口定义了任务编排的方法,执行某一阶段,可以向下执行后续阶段。异步执行的,默认线程池是ForkJoinPool.commonPool(),但为了业务之间互不影响,且便于定位问题,强烈推荐使用自定义线程池。
CompletableFuture中默认线程池如下:
private static final boolean useCommonPool =
(ForkJoinPool.getCommonPoolParallelism() > 1);
/**
* Returns the targeted parallelism level of the common pool.
*/
public static int getCommonPoolParallelism() {
/**
* Common pool parallelism. To allow simpler use and management
* when common pool threads are disabled, we allow the underlying
* common.parallelism field to be zero, but in that case still report
* parallelism as 1 to reflect resulting caller-runs mechanics.
*/
return commonParallelism;
}
/**
* Default executor -- ForkJoinPool.commonPool() unless it cannot support parallelism.
*/
private static final Executor asyncPool = useCommonPool ?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
1 结果处理
public class CompletableFutureTest {
static ThreadPoolExecutor executor = new ThreadPoolExecutor(5,
50,
10,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("main start ...");
// runAsync();
// getIntegerCompletableFuture();
// getIntegerCompletableFuture_2();
// getIntegerCompletableFuture_3();
// runAsync_2();
// getIntegerCompletableFuture_4();
getIntegerCompletableFuture_5();
System.out.println("main end ...");
}
}
异步发出请求,supplyAsync 会有接口的返回,future.get() 会阻塞等待结果的执行完毕。
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,
Executor executor) {
return asyncSupplyStage(screenExecutor(executor), supplier);
}
private static void getIntegerCompletableFuture() throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " start ...");
try {
// 模拟执行任务 。。。
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return 10;
}, executor);
System.out.println(future.get());
/**
* 情况1、future.get() 注释掉
* main start ...
* main end ...
* pool-1-thread-1 start ...
*
* 情况2、future.get() 存在
* main start ...
* pool-1-thread-1 start ...
* 10
* main end ...
*/
}
1 结果处理-whenCompleteAsync
当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的 Action。主要是下面的方法:
public CompletableFuture<T> whenCompleteAsync(
BiConsumer<? super T, ? super Throwable> action, Executor executor) {
return uniWhenCompleteStage(screenExecutor(executor), action);
}
private static void getIntegerCompletableFuture_2() throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " start ...");
int a = 1 / 0;
return 10;
/**
* public CompletableFuture<T> whenCompleteAsync(
* BiConsumer<? super T, ? super Throwable> action, Executor executor){}
*/
}, executor).whenCompleteAsync((t, u) -> {
System.out.println("whenCompleteAsync start ...");
System.out.println("t = " + t);
System.out.println("u = " + u);
}, executor);
System.out.println(future.get());
/**
* int a = 1 / 0; 不存在
* main start ...
* pool-1-thread-1 start ...
* whenCompleteAsync start ...
* t = 10
* u = null
* 10
* main end ...
*/
/**
* int a = 1 / 0; 存在
* main start ...
* pool-1-thread-1 start ...
* whenCompleteAsync start ...
* t = null 特别注意:*********为null
* u = java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
* Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
* at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
* at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
* at com.thread.future.CompletableFuture学习.CompletableFutureTest.getIntegerCompletableFuture_2(CompletableFutureTest.java:72)
* at com.thread.future.CompletableFuture学习.CompletableFutureTest.main(CompletableFutureTest.java:25)
* Caused by: java.lang.ArithmeticException: / by zero
* at com.thread.future.CompletableFuture学习.CompletableFutureTest.lambda$getIntegerCompletableFuture_2$1(CompletableFutureTest.java:65)
* at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
* at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
* at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
* at java.lang.Thread.run(Thread.java:748)
*/
}
private static void runAsync() {
CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName() + " start ...");
/**
* public CompletableFuture<T> whenCompleteAsync(
* BiConsumer<? super T, ? super Throwable> action, Executor executor){}
*/
}, executor).whenCompleteAsync((t, u) -> {
System.out.println("whenCompleteAsync start ...");
System.out.println("t = " + t);
System.out.println("u = " + u);
}, executor);
/**
* main start ...
* pool-1-thread-1 start ...
* main end ...
* whenCompleteAsync start ...
* t = null
* u = null
*/
}
2 结果处理-exceptionally
返回一个新的CompletableFuture,当前面的CompletableFuture完成时,它也完成;当它异常完成时,给定函数的异常触发这个CompletableFuture的完成。
private static void getIntegerCompletableFuture_3() throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " start ...");
int a = 1 / 0;
return 10;
}, executor).whenCompleteAsync((t, u) -> {
System.out.println("whenCompleteAsync start ...");
System.out.println("t = " + t);
System.out.println("u = " + u);
/**
* public CompletableFuture<T> exceptionally(
* Function<Throwable, ? extends T> fn) {}
*/
}, executor).exceptionally(ex -> {
System.out.println("ex = " + ex);
return 100;
});
System.out.println(future.get());
/**
* main start ...
* pool-1-thread-1 start ...
* whenCompleteAsync start ...
* t = null
* u = java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
* ex = java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
* 100
* main end ...
*/
}
3 handleAsync
private static void getIntegerCompletableFuture_4() throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " start ...");
int a = 1 / 0;
return 10;
}, executor).handleAsync((res, ex) -> {
System.out.println("res = " + res);
System.out.println("ex = " + ex);
return 100;
});
System.out.println(future.get());
/**
* main start ...
* pool-1-thread-1 start ...
* res = null
* ex = java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
* 100
* main end ...
*/
}
private static void getIntegerCompletableFuture_5() throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " start ...");
return 10;
}, executor).handleAsync((res, ex) -> {
System.out.println("res = " + res);
System.out.println("ex = " + ex);
if (Objects.nonNull(ex)) {
return 100;
}
return res;
});
System.out.println(future.get());
/**
* main start ...
* pool-1-thread-1 start ...
* res = 10
* ex = null
* 10
* main end ...
*/
}
private static void runAsync_2() {
CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName() + " start ...");
/**
* public <U> CompletionStage<U> handleAsync
* (BiFunction<? super T, Throwable, ? extends U> fn,
* Executor executor);
*/
}, executor).handleAsync((res, ex) -> {
System.out.println("res = " + res);
System.out.println("ex = " + ex);
return null;
}, executor);
/**
* main start ...
* pool-1-thread-1 start ...
* main end ...
* res = null
* ex = null
*/
}
2 对线程任务结果的一种消费函数
public class CompletableFuture串行Test {
static ThreadPoolExecutor executor = new ThreadPoolExecutor(5,
50,
10,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("main start ...");
// runAsync_1();
// runAsync_2();
// supplyAsync();
supplyAsync_2();
System.out.println("main end ...");
}
}
1 thenRunAsync
thenRun也是对线程任务结果的一种消费函数,与thenAccept不同的是,thenRun会在上一阶段 CompletableFuture计算完成的时候执行一个Runnable,而Runnable并不使用该CompletableFuture计算的结果。
private static void runAsync_1() {
CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName() + " start ...");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
/**
* public CompletionStage<Void> thenRunAsync(Runnable action, Executor executor);
*/
, executor).thenRunAsync(() -> System.out.println(Thread.currentThread().getName() + " start ..."), executor);
/**
* main start ...
* pool-1-thread-1 start ...
* main end ...
* pool-1-thread-2 start ... (等待了3s)
*/
}
2 thenAcceptAsync
thenRun也是对线程任务结果的一种消费函数,与thenAccept不同的是,thenRun会在上一阶段 CompletableFuture计算完成的时候执行一个Consumer,而Consumer使用该CompletableFuture计算的结果。观察该系列函数的参数类型可知,它们是函数式接口Consumer,这个接口只有输入,没有返回值。
private static void runAsync_2() {
CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName() + " start ...");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
/**
* public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor);
*/
, executor).thenAcceptAsync(t -> {
System.out.println("t = " + t);
System.out.println(Thread.currentThread().getName() + " start ...");
}, executor);
/**
* main start ...
* pool-1-thread-1 start ...
* main end ...
* t = null
* pool-1-thread-2 start ...
*/
}
private static void supplyAsync() throws ExecutionException, InterruptedException {
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " start ...");
return 10;
/**
* public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,
* Executor executor);
*/
}, executor).thenAcceptAsync(t -> {
System.out.println("t = " + t);
}, executor);
System.out.println(future.get());
/**
* main start ...
* pool-1-thread-1 start ...
* t = 10
* null
* main end ...
*/
}
private static void supplyAsync_2() throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " start ...");
return 10;
/**
* public <U> CompletionStage<U> thenApplyAsync
* (Function<? super T,? extends U> fn,
* Executor executor);
*/
}, executor).thenApplyAsync(t -> {
System.out.println("t = " + t);
return t * 100;
}, executor);
System.out.println(future.get());
/**
* main start ...
* pool-1-thread-1 start ...
* t = 10
* 1000
* main end ...
*/
}
3 任务交互-完成一个
线程交互指将两个线程任务获取结果的速度相比较,按一定的规则进行下一步处理。
public class CompletableFuture只完成一个任务Test {
static ThreadPoolExecutor executor = new ThreadPoolExecutor(5,
50,
10,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("main start ...");
CompletableFuture<Integer> future_1 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " future_1-start ...");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " future_1-end ...");
return 66;
}, executor);
CompletableFuture<Integer> future_2 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " future_2-start ...");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " future_2-end ...");
return 77;
}, executor);
// runAfterEitherAsync(future_1, future_2);
// runAfterEitherAsync_2(future_1, future_2);
runAfterEitherAsync_3(future_1, future_2);
}
}
1 runAfterEitherAsync
两个线程任务相比较,有任何一个执行完成,就进行下一步操作,不关心运行结果。
private static void runAfterEitherAsync(CompletableFuture<Integer> future_1, CompletableFuture<Integer> future_2) {
/**
* public CompletionStage<Void> runAfterEitherAsync
* (CompletionStage<?> other,
* Runnable action,
* Executor executor);
*/
CompletableFuture<Void> voidCompletableFuture = future_1.runAfterEitherAsync(future_2, () -> {
System.out.println(Thread.currentThread().getName() + " runAfterEitherAsync-start ...");
}, executor);
System.out.println("main end ...");
/**
* main start ...
* pool-1-thread-1 future_1-start ...
* pool-1-thread-2 future_2-start ...
* main end ...
* pool-1-thread-1 future_1-end ...
* pool-1-thread-3 runAfterEitherAsync-start ...
* pool-1-thread-2 future_2-end ...
*/
}
2 acceptEitherAsync
两个线程任务相比较,先获得执行结果的,就对该结果进行下一步的消费操作。
private static void runAfterEitherAsync_2(CompletableFuture<Integer> future_1, CompletableFuture<Integer> future_2) {
/**
* public CompletionStage<Void> acceptEitherAsync
* (CompletionStage<? extends T> other,
* Consumer<? super T> action,
* Executor executor);
*/
future_1.acceptEitherAsync(future_2, f1 -> {
System.out.println("f1 = " + f1);
}, executor);
System.out.println("main end ...");
/**
* main start ...
* pool-1-thread-1 future_1-start ...
* pool-1-thread-2 future_2-start ...
* main end ...
* pool-1-thread-1 future_1-end ...
* f1 = 66
* pool-1-thread-2 future_2-end ...
*/
}
3 applyToEitherAsync
两个线程任务相比较,先获得执行结果的,就对该结果进行下一步的转化操作。
private static void runAfterEitherAsync_3(CompletableFuture<Integer> future_1, CompletableFuture<Integer> future_2) throws ExecutionException, InterruptedException {
/**
* public <U> CompletableFuture<U> applyToEitherAsync(
* CompletionStage<? extends T> other, Function<? super T, U> fn,
* Executor executor) {}
*/
CompletableFuture<Integer> future = future_1.applyToEitherAsync(future_2, f1 -> {
Integer f3 = f1 * 1000;
System.out.println("f3 = " + f3);
return f3;
}, executor);
System.out.println(future.get());
System.out.println("main end ...");
/**
* main start ...
* pool-1-thread-1 future_1-start ...
* pool-1-thread-2 future_2-start ...
* pool-1-thread-1 future_1-end ...
* f3 = 66000
* 66000
* main end ...
* pool-1-thread-2 future_2-end ...
*/
}
4 任务交互-都完成
public class CompletableFuture任务都完成Test {
static ThreadPoolExecutor executor = new ThreadPoolExecutor(5,
50,
10,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
System.out.println("main start ...");
CompletableFuture<Integer> future_1 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " future_1-start ...");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " future_1-end ...");
return 66;
}, executor);
CompletableFuture<Integer> future_2 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " future_2-start ...");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " future_2-end ...");
return 77;
}, executor);
// runAfterBothAsync(future_1, future_2);
// runAfterBothAsync_1(future_1, future_2);
runAfterBothAsync_2(future_1, future_2);
}
}
1 runAfterBothAsync
private static void runAfterBothAsync(CompletableFuture<Integer> future_1, CompletableFuture<Integer> future_2) {
/**
* public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other,
* Runnable action){}
*/
future_1.runAfterBothAsync(future_2, () -> {
System.out.println(Thread.currentThread().getName() + " runAfterBothAsync-start ...");
}, executor);
System.out.println("main end ...");
/**
* main start ...
* pool-1-thread-1 future_1-start ...
* pool-1-thread-2 future_2-start ...
* main end ...
* pool-1-thread-2 future_2-end ...
* pool-1-thread-1 future_1-end ...
* (最后执行 。。。)
* pool-1-thread-3 runAfterBothAsync-start ...
*/
}
2 thenAcceptBothAsync
thenAcceptBoth函数的作用是,当两个CompletionStage都正常完成计算的时候,就会执行提供的action消费两个异步的结果。
private static void runAfterBothAsync_1(CompletableFuture<Integer> future_1, CompletableFuture<Integer> future_2) {
/**
* public <U> CompletionStage<Void> thenAcceptBothAsync
* (CompletionStage<? extends U> other,
* BiConsumer<? super T, ? super U> action,
* Executor executor);
*/
future_1.thenAcceptBothAsync(future_2, (f1, f2) -> {
System.out.println("f1 = " + f1);
System.out.println("f2 = " + f2);
}, executor);
System.out.println("main end ...");
/**
* main start ...
* pool-1-thread-1 future_1-start ...
* pool-1-thread-2 future_2-start ...
* main end ...
* pool-1-thread-2 future_2-end ...
* pool-1-thread-1 future_1-end ...
* f1 = 66
* f2 = 77
*/
}
3 thenCombineAsync
合并两个线程任务的结果,并进一步处理。
private static void runAfterBothAsync_2(CompletableFuture<Integer> future_1, CompletableFuture<Integer> future_2) {
/**
* public <U,V> CompletionStage<V> thenCombineAsync
* (CompletionStage<? extends U> other,
* BiFunction<? super T,? super U,? extends V> fn,
* Executor executor);
*/
future_1.thenCombineAsync(future_2, (f1, f2) -> {
Integer f3 = f1 + f2;
System.out.println("f3 = " + f3);
return f3;
}, executor);
System.out.println("main end ...");
/**
* main start ...
* pool-1-thread-1 future_1-start ...
* pool-1-thread-2 future_2-start ...
* main end ...
* pool-1-thread-1 future_1-end ...
* pool-1-thread-2 future_2-end ...
* f3 = 143
*/
}
5 组合任务
public class CompletableFuture组合任务Test {
static ThreadPoolExecutor executor = new ThreadPoolExecutor(5,
50,
10,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("main start ...");
CompletableFuture<Integer> future_1 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " future_1-start ...");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " future_1-end ...");
return 11;
}, executor);
CompletableFuture<Integer> future_2 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " future_2-start ...");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " future_2-end ...");
return 22;
}, executor);
CompletableFuture<Integer> future_3 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " future_3-start ...");
try {
TimeUnit.SECONDS.sleep(8);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " future_3-end ...");
return 33;
}, executor);
// allOf(future_1, future_2, future_3);
// allOf_2(future_1, future_2, future_3);
anyOf(future_1, future_2, future_3);
}
}
1 allOf
当所有给定的 CompletableFuture 完成时,返回一个新的 CompletableFuture
private static void allOf(CompletableFuture<Integer> future_1, CompletableFuture<Integer> future_2, CompletableFuture<Integer> future_3) {
/**
* public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs){}
*/
CompletableFuture<Void> future = CompletableFuture.allOf(future_1, future_2, future_3);
System.out.println("main end ...");
/**
* main start ...
* pool-1-thread-1 future_1-start ...
* pool-1-thread-2 future_2-start ...
* pool-1-thread-3 future_3-start ...
* main end ...
* pool-1-thread-1 future_1-end ...
* pool-1-thread-2 future_2-end ...
* pool-1-thread-3 future_3-end ...
*/
}
private static void allOf_2(CompletableFuture<Integer> future_1, CompletableFuture<Integer> future_2, CompletableFuture<Integer> future_3) throws ExecutionException, InterruptedException {
/**
* public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs){}
*/
CompletableFuture<Void> future = CompletableFuture.allOf(future_1, future_2, future_3);
future.get();
System.out.println("main end ...");
/**
*main start ...
* pool-1-thread-1 future_1-start ...
* pool-1-thread-2 future_2-start ...
* pool-1-thread-3 future_3-start ...
* pool-1-thread-1 future_1-end ...
* pool-1-thread-2 future_2-end ...
* pool-1-thread-3 future_3-end ...
* main end ...
*/
}
2 anyOf
当任何一个给定的CompletablFuture完成时,返回一个新的CompletableFuture
private static void anyOf(CompletableFuture<Integer> future_1, CompletableFuture<Integer> future_2, CompletableFuture<Integer> future_3) throws ExecutionException, InterruptedException {
/**
* public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
*/
CompletableFuture<Object> future = CompletableFuture.anyOf(future_1, future_2, future_3);
future.get();
System.out.println("main end ...");
/**
* main start ...
* pool-1-thread-1 future_1-start ...
* pool-1-thread-2 future_2-start ...
* pool-1-thread-3 future_3-start ...
* pool-1-thread-1 future_1-end ...
* main end ...
* pool-1-thread-2 future_2-end ...
* pool-1-thread-3 future_3-end ...
*/
}