Why are two Java threads (in some cases) more than twice as fast as one?
-
22-07-2019 - |
Question
File: Example1.java
public class Example1 implements Runnable {
public void run() {
for(int i = 0; i < 100000000; i++) {
int x = 5;
x = x * 4;
x = x % 3;
x = x + 9000;
x = x * 923;
}
}
public static void task() {
for(int i = 0; i < 100000000; i++) {
int x = 5;
x = x * 4;
x = x % 3;
x = x + 9000;
x = x * 923;
}
for(int i = 0; i < 100000000; i++) {
int x = 9;
x = x * 2;
x = x % 4;
x = x + 3241;
x = x * 472;
}
}
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
Example1.task();
Example1.task();
Example1.task();
Example1.task();
Example1.task();
long stopTime = System.currentTimeMillis();
long runTime = stopTime - startTime;
System.out.println("Run time for one thread: " + runTime);
startTime = System.Example1();
(new Thread(new Example1())).start();
(new Thread(new Example2())).start();
(new Thread(new Example1())).start();
(new Thread(new Example2())).start();
(new Thread(new Example1())).start();
(new Thread(new Example2())).start();
(new Thread(new Example1())).start();
(new Thread(new Example2())).start();
(new Thread(new Example1())).start();
(new Thread(new Example2())).start();
stopTime = System.currentTimeMillis();
runTime = stopTime - startTime;
System.out.println("Run time for two threads: " + runTime);
}
}
File: Example2.java
public class Example2 implements Runnable {
public void run() {
for(int i = 0; i < 100000000; i++) {
int x = 9;
x = x * 2;
x = x % 4;
x = x + 3241;
x = x * 472;
}
}
}
When I run this, It outputs:
Run time for one thread: 1219
Run time for two threads: 281
or something very close.
Why is there such a difference? Why does splitting it into two threads go more than two times faster than just running it directly?
Solution
You're actually not waiting for the threads to finish at all.
Once you start a thread, you must then call .join() on it to wait for it to complete. What's happening here is that all of your threads are starting and as soon as the last one has started, you clock it and then calculate the stop time. This means that your threads are still running in the background.
Edit: The reason why the first one takes so long is because you're making a series of synchronous calls, while creating a thread and starting it spawns an asynchronous task.
Edit 2: Here's a napkin sequence diagram of what happens in your first test: http://www.websequencediagrams.com/cgi-bin/cdraw?lz=TWFpbi0-RXhhbXBsZTE6IFRhc2sgc3RhcnRlZAphY3RpdmF0ZSAAGAgKACEILS0-TWFpbjogZG9uZQpkZQAYEgABWAABWAABgTFlMQo&s=napkin
Here's a napkin sequence diagram of what happens in your second test: http://www.websequencediagrams.com/cgi-bin/cdraw?lz=TWFpbi0tPkFub255bW91cyBUaHJlYWQ6IFN0YXJ0IEV4YW1wbGUxLnRhc2soKQoACSYyAAEuAAFdAAGBOwCCPjoAgyIGPk1haW46ICJIb3cgbG9uZyBkaWQgdGhhdCB0YWtlPyIKAINmEC0AKwhUYXNrcyBiZWdpbiB0byBmaW5pc2guLi4gKHNvbWUgbWF5IGhhdmUgZW5kZWQgZWFybGllcikK&s=napkin
Edit 3: I just realized that the second sequence diagram points all of the arrows to the /same/ thread. They are in fact DIFFERENT threads, each call.
OTHER TIPS
The call start() on a Thread immediately returns because it just enqueues the thread. The thread itself will begin running in the background some time later.
Here is what I get with your code adding join to the threads:
Run time for one thread: 566
Run time for two threads: 294
So previous answers are correct.
EDIT: I added joins this way. You can do it better, but it doesn't matter:
Thread[] t = new Thread[10];
(t[0] = new Thread(new Example1())).start();
(t[1] = new Thread(new Example2())).start();
(t[2] = new Thread(new Example1())).start();
(t[3] = new Thread(new Example2())).start();
(t[4] = new Thread(new Example1())).start();
(t[5] = new Thread(new Example2())).start();
(t[6] = new Thread(new Example1())).start();
(t[7] = new Thread(new Example2())).start();
(t[8] = new Thread(new Example1())).start();
(t[9] = new Thread(new Example2())).start();
for (Thread t1: t) {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
You have to join each thread. However, you don't waste your time waiting in join() because other threads are not blocked. If the thread has finished it's executution before you call to join, you just continue to next thread.
Also, what does your last comment mean?