Consider these two micro-benchmarks:
The first benchmark simply creates, starts and joins threads. The thread's Runnable
does no work.
public class ThreadTest {
public static void main(String[] args) throws Exception {
while (true) {
long start = System.nanoTime();
for (int i = 0; i < 100_000; i++) {
Thread t = new Thread(new Runnable() {
public void run() {
}});
t.start();
t.join();
}
long end = System.nanoTime();
System.out.println((end - start) / 100_000.0);
}
}
}
$ java ThreadTest
34627.91355
33596.66021
33661.19084
33699.44895
33603.097
33759.3928
33671.5719
33619.46809
33679.92508
33500.32862
33409.70188
33475.70541
33925.87848
33672.89529
^C
On a typical modern PC running Linux with 64bit Java 8 u101, this benchmark shows an average time taken to create, start and join thread of between 33.6 and 33.9 microseconds.
The second benchmark does the equivalent to the first but using an ExecutorService
to submit tasks and a Future
to rendezvous with the end of the task.
import java.util.concurrent.*;
public class ExecutorTest {
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
while (true) {
long start = System.nanoTime();
for (int i = 0; i < 100_000; i++) {
Future<?> future = exec.submit(new Runnable() {
public void run() {
}
});
future.get();
}
long end = System.nanoTime();
System.out.println((end - start) / 100_000.0);
}
}
}
$ java ExecutorTest
6714.66053
5418.24901
5571.65213
5307.83651
5294.44132
5370.69978
5291.83493
5386.23932
5384.06842
5293.14126
5445.17405
5389.70685
^C
As you can see, the averages are between 5.3 and 5.6 microseconds.
While the actual times will depend on a variety of factors, the difference between these two results is significant. It is clearly faster to use a thread pool to recycle threads than it is to create new threads.