I'm learning about Java Threads and I've encountered two different ways to create a thread. I'm not sure about the technical differences between them:
// Approach 1
Thread n = new Thread(new NumberGenerator(100));
// Approach 2
Thread n = new Thread(new NumberGenerator(100)::run);
Here's my complete example code for context:
public class NumberGenerator implements Runnable {
private int limit;
public NumberGenerator(int limit) {
this.limit = limit;
}
@Override
public void run() {
for(int i = 0; i < limit; i++) {
System.out.println(i);
}
}
}
public class Main {
public static void main(String[] args) {
// Which approach is better and why?
Thread t1 = new Thread(new NumberGenerator(100));
Thread t2 = new Thread(new NumberGenerator(100)::run);
t1.start();
t2.start();
}
}
I've tested both approaches and they seem to work similarly, but I want to understand the underlying differences and best practices.
As fas as I know, both approaches use this constructor on Thread.java
public Thread(Runnable target) {
this(null, target, "Thread-" + nextThreadNum(), 0);
}
So, can they be considered as subclasses of Runnable? What are the conditions to determine if an object is Runnable in this case? What are the technical differences between these two approaches?
There is roughly no difference.
If you examine the bytecode (using javap -c Main
):
public static void main(java.lang.String[]);
Code:
0: new #7 // class java/lang/Thread
3: dup
4: new #9 // class NumberGenerator
7: dup
8: bipush 100
10: invokespecial #11 // Method NumberGenerator."<init>":(I)V
13: invokespecial #14 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
16: astore_1
17: new #7 // class java/lang/Thread
20: dup
21: new #9 // class NumberGenerator
24: dup
25: bipush 100
27: invokespecial #11 // Method NumberGenerator."<init>":(I)V
30: invokedynamic #17, 0 // InvokeDynamic #0:run:(LNumberGenerator;)Ljava/lang/Runnable;
35: invokespecial #14 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
38: astore_2
39: aload_1
40: invokevirtual #21 // Method java/lang/Thread.start:()V
43: aload_2
44: invokevirtual #21 // Method java/lang/Thread.start:()V
47: return
The only difference in between the two calls is that the second one involves invokedynamic
.
It is not so easy to tell the difference but in the first case you gave the thread a Runnable
so it will be bable to call its method run
when appropriate (start
), and in the second case you tell the thread that you explicitly want run
to be called when it will call run
at start
. Nothing interesting here.
Since Java 8, you could use in place of an object that implements a run
method, any object that has a method whose signature is the same by refering directly to this method:
class Test {
public void myFunction() { // same signature as run
}
}
...
Test test = new Test();
Thread t = new Thread(test::myFunction);
t.start();
}
This tells JVM to executes myFunction
when start
executes a call to run
.
For more details, read about Functional Programming in Java.