Executor
Executor making it easier to manage thread execution. Instead of manage thread manually, you submit tasks to Executor, the tasks will be run concurrently within a pool of threads.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
});
executor.submit(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
});
executor.shutdown();
}
}
The output shoul look like following:
pool-1-thread-1 0
pool-1-thread-1 1
pool-1-thread-1 2
pool-1-thread-1 3
pool-1-thread-1 4
pool-1-thread-1 5
pool-1-thread-2 0
pool-1-thread-1 6
pool-1-thread-2 1
pool-1-thread-2 2
pool-1-thread-2 3
pool-1-thread-2 4
pool-1-thread-2 5
pool-1-thread-2 6
pool-1-thread-2 7
pool-1-thread-2 8
pool-1-thread-2 9
pool-1-thread-1 7
pool-1-thread-1 8
pool-1-thread-1 9
Submit more tasks:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
Runnable task = () -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
executor.submit(task);
executor.submit(task);
executor.submit(task);
executor.shutdown();
}
}
Still two threads running at the same time:
pool-1-thread-2 0
pool-1-thread-1 0
pool-1-thread-1 1
pool-1-thread-2 1
pool-1-thread-2 2
pool-1-thread-1 2
pool-1-thread-2 3
pool-1-thread-1 3
pool-1-thread-1 4
pool-1-thread-2 4
pool-1-thread-2 0
pool-1-thread-2 1
pool-1-thread-2 2
pool-1-thread-2 3
pool-1-thread-2 4
Virtual Threads
Using virtual threads via Executors.newVirtualThreadPerTaskExecutor():
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
public class Main {
public static void main(String[] args) throws InterruptedException {
Instant startTime = Instant.now();
// ExecutorService executor = Executors.newCachedThreadPool();
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
AtomicLong memUsage = new AtomicLong(0L);
for (int i = 0; i < 100_000; i++) {
executor.submit(() -> {
try {
Thread.sleep(100);
Long usage = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024 / 1024;
if (memUsage.get() < usage) {
memUsage.set(usage);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executor.shutdown();
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
System.out.println("Execution time: " + Duration.between(startTime, Instant.now()) + " Memory: " + memUsage.get());
}
}
It's obvious that virtual threads are much more efficient than the platform thread:
Execution time: PT4.35937S Memory: 1876
Execution time: PT0.410156S Memory: 203