Search code examples
javamultithreadingobjectthread-safetyinstance

Why calling class function from a thread works in Java?


So I got this weird scenario that works fine but doesn't make any sense as to why it works. From my experience in C++, I'm very sure that this will not work at all and will throw an error during compilation.

public class Practice {

    private void testFunction() {
        System.out.println("working fine");
        System.out.println("testFunc: " + this);
    }

    public void start() {
        System.out.println("start: " + this);

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("run: " + this);
                testFunction();
            }
        }).start();
    }
}

// Inside main
Practice practice = new Practice();
practice.start()

Output

start: com.company.Practice@5e2de80c
run: com.company.Practice$1@6720b744
working fine
testFunc: com.company.Practice@5e2de80c

WHY!? why did this work? How am I able to call testFunction() from a Runnable? Shouldn't I create a new instance and then call that function like Practice p = new Practice(); p.testFunction()? How does Java know that testFunction() is part of Practice class and not Runnable?

And also, how come the value of this in testFunction() is same as start()? Shouldn't it be same as run()?


Solution

  • You can access testFunction is because you are using the simple name of testFunction. If instead you did:

    this.testFunction();
    

    Then you wouldn't be able to access it.

    When you call a method with its simple name, everything that is in scope is considered to resolve the method. (See JLS 6.5.7.1) testFunction is definitely in scope inside the Runnable, because it is inside Practice.

    To call testFunction, you still need an instance of Practice, right? Well, the compiler generates some code that passes the instance of Practice on which start is invoked (i.e. what this means in start) to the instance of Runnable that you are creating. Specifically, it creates a non-static inner class

    class Practice {
        public void start() {
            ...
            new Thread(new SomeGeneratedRunnable()).start();
        }
        // not the actual name of the class. The actual name is Practice$1
        private class SomeGeneratedRunnable implements Runnable {
            public void run() {
                System.out.println("run: " + this);
                testFunction();
            }
        }
    }
    

    You can think of this as:

    class Practice {
        public void start() {
            ...
            new Thread(new SomeGeneratedRunnable(this)).start();
        }
    }
    
    class SomeGeneratedRunnable implements Runnable {
        private final Practice outer;
    
        public SomeGeneratedRunnable(Practice outer) {
            this.outer = outer;
        }
    
        public void run() {
            System.out.println("run: " + this);
            outer.testFunction();
        }
    }
    

    Now you should see why this inside testFunction can't possibly be the same as the this inside the Runnable. The this inside Runnable is an instance of SomeGeneratedRunnable (or Practice$1), but this inside testFunction is an instance of Practice.