Photo by Christian Holzinger on Unsplash

Java executors and thread pools

Introduction

The Thread class

Runnable runnable = new Runnable() { // task creation
@Override
public void run() {
logger.info("run..."); // task execution
}
};
Thread thread = new Thread(runnable); // task submission during thread creation
thread.start(); // thread start
thread.join(); // thread termination
Executor executor = createExecutor(); // thread creation and thread startRunnable task = createTask(); // task creation
Future future = executor.submit(task); // task submission
future.get(); // waiting until task is executed
executor.terminate(); // thread termination

Executors

The Executor interface

Runnable runnable = () -> logger.info("run...");Executor executor = new Executor() {
@Override
public void execute(Runnable command) {
command.run();
}
};
executor.execute(runnable);
Runnable runnable = () -> logger.info("run...");Executor executor = new Executor() {
@Override
public void execute(Runnable command) {
new Thread(command).start();
}
};
executor.execute(runnable);
Runnable runnable = () -> logger.info("run...");Executor executor = Executors.newSingleThreadExecutor();executor.execute(runnable);

The ExecutorService interface

ExecutorService executorService = Executors.newSingleThreadExecutor();Runnable runnable = () -> System.out.println("run...");
Future<?> future = executorService.submit(runnable);
System.out.println("result: " + future.get()); // null
executorService.shutdown();
ExecutorService executorService = Executors.newSingleThreadExecutor();Runnable runnable = () -> System.out.println("run...");
Future<String> future = executorService.submit(runnable, "Alpha");
System.out.println("result: " + future.get());
executorService.shutdown();
ExecutorService executorService = Executors.newSingleThreadExecutor();Callable<String> callable = () -> "Bravo";
Future<String> future = executorService.submit(callable);
System.out.println("result: " + future.get());
executorService.shutdown();
ExecutorService executorService = Executors.newCachedThreadPool();List<Callable<String>> callables = Arrays.asList(
() -> sleepAndGet(2, "Bravo"),
() -> sleepAndGet(1, "Alpha"),
() -> sleepAndGet(3, "Charlie")
);
List<String> results = executorService.invokeAll(callables)
.stream()
.peek(future -> logger.info("is done: {}, is cancelled: {}",
future.isDone(),
future.isCancelled()))
.map(future -> {
try {
return future.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList());
logger.info("results: {}", results);executorService.shutdown();
ExecutorService executorService = Executors.newCachedThreadPool();List<Callable<String>> callables = Arrays.asList(
() -> sleepAndGet(2, "Bravo"),
() -> sleepAndGet(1, "Alpha"),
() -> sleepAndGet(3, "Charlie")
);
String result = executorService.invokeAny(callables);
logger.info("result: {}", result);
executorService.shutdown();
ExecutorService executorService = Executors.newSingleThreadExecutor();executorService.submit(() -> sleepAndGet(1, "Alpha"));
executorService.submit(() -> sleepAndGet(1, "Bravo"));
executorService.submit(() -> sleepAndGet(1, "Charlie"));
logger.info("is shutdown: {}", executorService.isShutdown());logDuration(
"shutdown",
() -> executorService.shutdown()
);
logger.info("is shutdown: {}", executorService.isShutdown());
ExecutorService executorService = Executors.newSingleThreadExecutor();logger.info("is shutdown: {}", executorService.isShutdown());
executorService.shutdown();
logger.info("is shutdown: {}", executorService.isShutdown());
executorService.submit(() -> "Alpha"); // java.util.concurrent.RejectedExecutionException
ExecutorService executorService = Executors.newSingleThreadExecutor();executorService.submit(() -> sleepAndGet(1, "Alpha"));
executorService.submit(() -> sleepAndGet(1, "Bravo"));
executorService.submit(() -> sleepAndGet(1, "Charlie"));
logger.info("is terminated: {}", executorService.isTerminated());logDuration(
"shutdownNow",
() -> {
List<Runnable> skippedTasks = executorService.shutdownNow();
logger.info("count of tasks never commenced execution: {}", skippedTasks.size());
}
);
logger.info("is terminated: {}", executorService.isTerminated());
ExecutorService executorService = Executors.newSingleThreadExecutor();executorService.submit(() -> sleepAndGet(1, "Alpha"));
executorService.submit(() -> sleepAndGet(1, "Bravo"));
executorService.submit(() -> sleepAndGet(1, "Charlie"));
executorService.shutdown();logDuration(
"awaitTermination",
() -> executorService.awaitTermination(60, TimeUnit.SECONDS)
);
logger.debug("executor service: shutdown started");
executorService.shutdown();
try {
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
List<Runnable> skippedTasks = executorService.shutdownNow();
logger.error("count of tasks never commenced execution: {}", skippedTasks.size());
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
logger.error("executor service didn't terminate");
}
}
} catch (InterruptedException e) {
logger.error("executor service is interrupted", e);
executorService.shutdownNow();
Thread.currentThread().interrupt();
}
logger.debug("executor service: shutdown finished");

The ScheduledExecutorService interface

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);Runnable runnable = () -> logger.info("finished");logger.info("started");
ScheduledFuture<?> scheduledFuture = scheduledExecutorService.schedule(runnable, 3000, TimeUnit.MILLISECONDS);
TimeUnit.MILLISECONDS.sleep(1000);long remainingDelay = scheduledFuture.getDelay(TimeUnit.MILLISECONDS);
logger.info("remaining delay: {} millisecond(s)", remainingDelay);
logger.info("result: {}", scheduledFuture.get());shutdown(scheduledExecutorService);
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);Callable<String> callable = () -> {
logger.info("finished");
return "Alpha";
};
logger.info("started");
ScheduledFuture<String> scheduledFuture = scheduledExecutorService.schedule(callable, 3000, TimeUnit.MILLISECONDS);
TimeUnit.MILLISECONDS.sleep(1000);long remainingDelay = scheduledFuture.getDelay(TimeUnit.MILLISECONDS);
logger.info("remaining delay: {} millisecond(s)", remainingDelay);
logger.info("result: {}", scheduledFuture.get());shutdown(scheduledExecutorService);
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);LocalTime start = LocalTime.now();Runnable runnable = () -> logger.info("duration from start: {} second(s)", Duration.between(start, LocalTime.now()).getSeconds());
int initialDelay = 3;
int period = 1;
ScheduledFuture<?> scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(runnable, initialDelay, period, TimeUnit.SECONDS);
Runnable canceller = () -> {
scheduledFuture.cancel(true);
scheduledExecutorService.shutdown();
};
scheduledExecutorService.schedule(canceller, 10, TimeUnit.SECONDS);
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);LocalTime start = LocalTime.now();Runnable runnable = () -> {
sleep(2);
logger.info("duration from start: {} second(s)", Duration.between(start, LocalTime.now()).getSeconds());
};
int initialDelay = 3;
int delay = 1;
ScheduledFuture<?> scheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(runnable, initialDelay, delay, TimeUnit.SECONDS);
Runnable canceller = () -> {
scheduledFuture.cancel(true);
scheduledExecutorService.shutdown();
};
scheduledExecutorService.schedule(canceller, 10, TimeUnit.SECONDS);

Thread pools

The ThreadPoolExecutor class

Threads creation

Threads termination

Tasks queueing

Tasks rejection

The ThreadFactory interface

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
logger.info("core pool size: {}", threadPoolExecutor.getCorePoolSize()); // 2
logger.info("maximum pool size: {}", threadPoolExecutor.getMaximumPoolSize()); // 4
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 10,
1L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(2));
for (int i = 0; i < 10; i++) {
threadPoolExecutor.submit(() -> sleep(1));
}
for (int i = 0; i < 10; i++) {
sleep(1);
logThreadPoolSize(threadPoolExecutor); if (threadPoolExecutor.isTerminated()) {
logThreadPoolSize(threadPoolExecutor);
break;
}
}
threadPoolExecutor.shutdown();
...
private static void logThreadPoolSize(ThreadPoolExecutor threadPoolExecutor) {
logger.info("thread pool size (active/current/maximum): {}/{}/{}",
threadPoolExecutor.getActiveCount(),
threadPoolExecutor.getPoolSize(),
threadPoolExecutor.getLargestPoolSize()
);
}
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 2,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
for (int i = 0; i < 10; i++) {
threadPoolExecutor.submit(() -> sleep(1));
}
for (int i = 0; i < 10; i++) {
sleep(1);
logTasksCount(threadPoolExecutor); if (threadPoolExecutor.isTerminated()) {
logTasksCount(threadPoolExecutor);
break;
}
}
threadPoolExecutor.shutdown();
...
private static void logTasksCount(ThreadPoolExecutor threadPoolExecutor) {
logger.info("tasks count (all/completed): {}/{}",
threadPoolExecutor.getTaskCount(),
threadPoolExecutor.getCompletedTaskCount());
}
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 10,
0L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(1));
for (int i = 0; i < 10; i++) {
threadPoolExecutor.submit(() -> sleep(1));
}
threadPoolExecutor.shutdown();for (int i = 0; i < 10; i++) {
sleep(1);
logThreadPoolSize(threadPoolExecutor); if (threadPoolExecutor.isTerminated()) {
logThreadPoolSize(threadPoolExecutor);
break;
}
}
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 10,
5L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(1));
for (int i = 0; i < 10; i++) {
threadPoolExecutor.submit(() -> sleep(1));
}
threadPoolExecutor.shutdown();for (int i = 0; i < 10; i++) {
sleep(1);
logThreadPoolSize(threadPoolExecutor); if (threadPoolExecutor.isTerminated()) {
logThreadPoolSize(threadPoolExecutor);
break;
}
}
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new SynchronousQueue<Runnable>());
System.out.println(threadPoolExecutor.getQueue().remainingCapacity());threadPoolExecutor.submit(() -> sleepAndGet(3, "Alpha"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Bravo"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Charlie"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Delta"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Echo"));
threadPoolExecutor.shutdown();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
System.out.println(threadPoolExecutor.getQueue().remainingCapacity());threadPoolExecutor.submit(() -> sleepAndGet(3, "Alpha"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Bravo"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Charlie"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Delta"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Echo"));
threadPoolExecutor.shutdown();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(3));
System.out.println(threadPoolExecutor.getQueue().remainingCapacity());threadPoolExecutor.submit(() -> sleepAndGet(3, "Alpha"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Bravo"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Charlie"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Delta"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Echo"));
threadPoolExecutor.shutdown();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1),
new ThreadPoolExecutor.AbortPolicy());
try {
threadPoolExecutor.submit(() -> sleepAndGet(3, "Alpha"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Bravo"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Charlie")); // java.util.concurrent.RejectedExecutionException
threadPoolExecutor.submit(() -> sleepAndGet(3, "Delta"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Echo"));
} finally {
threadPoolExecutor.shutdown();
}
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1),
new ThreadPoolExecutor.CallerRunsPolicy());
threadPoolExecutor.submit(() -> sleepAndGet(3, "Alpha"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Bravo"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Charlie"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Delta"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Echo"));
threadPoolExecutor.shutdown();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1),
new ThreadPoolExecutor.DiscardPolicy());
threadPoolExecutor.submit(() -> sleepAndGet(3, "Alpha"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Bravo"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Charlie"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Delta"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Echo"));
threadPoolExecutor.shutdown();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1),
new ThreadPoolExecutor.DiscardOldestPolicy());
threadPoolExecutor.submit(() -> sleepAndGet(3, "Alpha"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Bravo"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Charlie"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Delta"));
threadPoolExecutor.submit(() -> sleepAndGet(3, "Echo"));
threadPoolExecutor.shutdown();
ThreadPoolExecutor threadPoolExecutor = new ExtendedThreadPoolExecutor();threadPoolExecutor.submit(() -> sleep(1));for (int i = 0; i < 10; i++) {
sleep(1);
logTasksCount(threadPoolExecutor); if (threadPoolExecutor.isTerminated()) {
logTasksCount(threadPoolExecutor);
break;
}
}
threadPoolExecutor.shutdown();
...
private static class ExtendedThreadPoolExecutor extends ThreadPoolExecutor {
private ExtendedThreadPoolExecutor() {
super(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
logger.info("before task execution: thread {}, task {}", t, r);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
logger.info("after task execution: task {}, exception {}", r, t);
}
@Override
protected void terminated() {
super.terminated();
logger.info("is terminated");
}
}

The ScheduledThreadPoolExecutor class

ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);Runnable runnable = () -> logger.info("finished");logger.info("started");
ScheduledFuture<?> scheduledFuture = scheduledThreadPoolExecutor.schedule(runnable, 3000, TimeUnit.MILLISECONDS);
TimeUnit.MILLISECONDS.sleep(1000);long remainingDelay = scheduledFuture.getDelay(TimeUnit.MILLISECONDS);
logger.info("remaining delay: {} millisecond(s)", remainingDelay);
logger.info("result: {}", scheduledFuture.get());shutdown(scheduledThreadPoolExecutor);

The ForkJoinPool class

public class PrimeNumbersCountRecursiveTask extends RecursiveTask<Long> {   private final int start;
private final int end;
private final int threshold;
public PrimeNumbersCountRecursiveTask(int start, int end, int threshold) {
this.start = start;
this.end = end;
this.threshold = threshold;
}
@Override
protected Long compute() {
if (((end + 1) - start) > threshold) {
return ForkJoinTask.invokeAll(getSubTasks())
.stream()
.mapToLong(ForkJoinTask::join)
.sum();
} else {
return findPrimeNumbersCount();
}
}
private List<PrimeNumbersCountRecursiveTask> getSubTasks() {
List<PrimeNumbersCountRecursiveTask> tasks = new ArrayList<>();
for (int i = 1; i <= end / threshold; i++) {
int end = i * threshold;
int start = (end - threshold) + 1;
tasks.add(new PrimeNumbersCountRecursiveTask(start, end, threshold));
}
return tasks;
}
private long findPrimeNumbersCount() {
long numbers = 0;
for (int n = start; n <= end; n++) {
if (isPrimeNumber(n)) {
numbers++;
}
}
return numbers;
}
private boolean isPrimeNumber(int n) {
if (n == 2) {
return true;
}
if (n == 1 || n % 2 == 0) {
return false;
}
int divisors = 0;
for (int i = 1; i <= n; i++) {
if (n % i == 0) {
divisors++;
}
}
return divisors == 2;
}
}
ForkJoinTask<Long> task = new PrimeNumbersCountRecursiveTask(1, 100000, 10);
ForkJoinPool forkJoinPool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());
forkJoinPool.execute(task);do {
sleep(100);
} while (!task.isDone());
System.out.println("count: " + task.getRawResult()); // 9592forkJoinPool.shutdown();
ForkJoinTask<Long> task = new PrimeNumbersCountRecursiveTask(1, 100000, 10);
ForkJoinPool forkJoinPool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());
Long count = forkJoinPool.invoke(task);
System.out.println("count: " + count); // 9592
forkJoinPool.shutdown();
ForkJoinTask<Long> task = new PrimeNumbersCountRecursiveTask(1, 100000, 10);
ForkJoinPool forkJoinPool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());
ForkJoinTask<Long> forkJoinTask = forkJoinPool.submit(task);
Long count = forkJoinTask.get();
System.out.println("count: " + count); // 9592
forkJoinPool.shutdown();
ForkJoinTask<Long> task = new PrimeNumbersCountRecursiveTask(1, 100000, 10);
ForkJoinPool forkJoinPool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());
forkJoinPool.execute(task);do {
logger.info("getParallelism={}, getPoolSize={}, getActiveThreadCount={}, getRunningThreadCount={}, isQuiescent={}",
forkJoinPool.getParallelism(),
forkJoinPool.getPoolSize(),
forkJoinPool.getActiveThreadCount(),
forkJoinPool.getRunningThreadCount(),
forkJoinPool.isQuiescent()
);
Thread.sleep(100);
} while (!task.isDone());
System.out.println("count: " + task.getRawResult()); // 9592forkJoinPool.shutdown();
ForkJoinTask<Long> task = new PrimeNumbersCountRecursiveTask(1, 100000, 10);
ForkJoinPool forkJoinPool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());
forkJoinPool.execute(task);do {
logger.info("getQueuedSubmissionCount={}, getQueuedTaskCount={}, getStealCount={}, hasQueuedSubmissions={}",
forkJoinPool.getQueuedSubmissionCount(),
forkJoinPool.getQueuedTaskCount(),
forkJoinPool.getStealCount(),
forkJoinPool.hasQueuedSubmissions()
);
Thread.sleep(100);
} while (!task.isDone());
System.out.println("count: " + task.getRawResult()); // 9592forkJoinPool.shutdown();

The Executors class

Factory methods for ExecutorService instances

public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}

Factory methods for ScheduledExecutorService instances

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1));
}

Factory methods for non-reconfigurable executors

ExecutorService executorService = Executors.unconfigurableExecutorService(
new ThreadPoolExecutor(1, 2,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>())
);
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorService; // java.lang.ClassCastException
ScheduledExecutorService scheduledExecutorService = Executors.unconfigurableScheduledExecutorService(
new ScheduledThreadPoolExecutor(1)
);
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = (ScheduledThreadPoolExecutor) scheduledExecutorService; // java.lang.ClassCastException

Factory methods for work-stealing executors

ForkJoinTask<Long> task = new PrimeNumbersCountRecursiveTask(1, 100000, 10);
ForkJoinPool forkJoinPool = (ForkJoinPool) Executors.newWorkStealingPool();
Long count = forkJoinPool.invoke(task);
System.out.println("count: " + count); // 9592

Factory methods for ThreadFactory instances

public static ThreadFactory defaultThreadFactory() {
return new DefaultThreadFactory();
}
private static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}

Factory methods for Callable instances

Runnable runnable = () -> System.out.println("running ...");
Callable<Object> callable = Executors.callable(runnable);
System.out.println("result: " + callable.call()); // null
Runnable runnable = () -> System.out.println("running ...");
Callable<Integer> callable = Executors.callable(runnable, 1);
System.out.println("result: " + callable.call()); // 1

Conclusion

Senior Software Engineer