javamultithreadingjava-11runnablemethod-reference

Understanding the difference between two Thread implementation approaches in Java?


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?


Solution

  • 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.