javamultithreadingloopsfor-loopcontext-switching

java: Why does not both threads execute in parallel


I am trying to understand the intrinsic locks in java. I have a program where I start 2 threads which will loop thru and call synchronized methods on a same object. I expect that both threads to execute things in parallel but Looks like it executes in sequence.

If I introduce a sleep in the loop then they execute in random order [as i expected]

public class Synchronized {

    private int valueM;

    public Synchronized( int value) {
        valueM = value;
    }

    synchronized
    public void one() throws InterruptedException
    {
        System.out.println("Object[" + valueM + "] executing one");
        Thread.sleep(100); //For case 2: comment it out
        System.out.println("Object[" + valueM + "] completed one");
    }

    synchronized
    public void two() throws InterruptedException
    {
        System.out.println("Object[" + valueM + "] executing two");
        Thread.sleep(100); //For case 2: comment it out
        System.out.println("Object[" + valueM + "] completed two");
    }

}

Test Code:

@org.junit.jupiter.api.Test
    void test_sync() throws InterruptedException
    {
        Synchronized obj = new Synchronized(1);

        Runnable task_one = new Runnable() {
            public void run() {
                for (int i=0 ; i<10; i++)
                {
                    try {
                        obj.one();
                        //Thread.sleep(100); //For case 2: uncomment it out
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        };

        Runnable task_two = new Runnable() {
            public void run() {
                for (int i=0 ; i<10; i++)
                {
                    try {
                        obj.two();
                        //Thread.sleep(100); //For case 2: uncomment it out
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        };

        Thread t1 = new Thread(task_one);
        Thread t2 = new Thread(task_two);

        t1.start();
        t2.start();

        t1.join();
        t2.join();
    }

Output:

Case 1: output:
Object[1] executing one
Object[1] completed one
...10times
Object[1] executing two
Object[1] completed two
...10times

Case 2: output: random order
Object[1] executing one
Object[1] completed one
Object[1] executing two
Object[1] completed two
...

UPDATE: The original issue is fixed.. Looks like it is random even in case 1 also, but I see it only when I load with more iterations (30K)..

So thread switching happens much less in a for loop with no sleeps? Is it something special to Java-JVM which tries to have for-loop to execute it as a "kind-of" atomic (not fully but as much as possible?) ?


Solution

  • Thread#start is a very slow method in relative terms. Counting to 10 (or counting to 1,000) does not take a computer very long. The first thread is done counting long before the operating system has done the work for the second thread to actually execute. If you want to actually start two threads "at the same time" you need to use a latch.

    Your test is also confounded by the fact that depending on your execution environment the system console writer may itself be a synchronized contested resource (or conversely it may not be guaranteed to flush and write in a point-in-time consistent way with the order threads accessed it.) Trying to use System.out.println to debug concurrency issues has caused many people much trouble over the years because the pause to acquire the console writer usually hides their memory consistency error.

    public static CountDownLatch latch = new CountDownLatch(1);
    
    public static class Thing implements Runnable {
        @Override
        public void run() {
            try {
                latch.await();
                //doStuff
            } catch (InterruptedException e) {
    
            }
        }
    }
    public static void main(String[] args) throws Exception {
    
        Thing thing1 = new Thing();
        Thing thing2 = new Thing();
        new Thread(thing1).start();
        new Thread(thing2).start();
        latch.countDown();
    }