Search code examples
javamultithreadingconcurrencysemaphorejava.util.concurrent

Why does this concurrent Java code often fail?


//Initially, I wanted to compare synchronized with Lock
public class SynchronizedVSLock {
    static final Lock lock = new ReentrantLock();
    static final int loopTime = 10;
    static final int numOfThread = 6;
    static final Random random = new Random();
    static final Semaphore runningThreadsNum = new Semaphore(numOfThread);
    public static void main(String[] args) throws InterruptedException {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < numOfThread - 1; i++) {
            new Thread(new Test1()).start();
        }
        new Thread(new Test1()).join();
        runningThreadsNum.acquire(numOfThread);
        long endTime = System.currentTimeMillis();
        System.out.println(endTime - startTime);
    }

    static class Test1 implements Runnable {
        @Override
        public void run() {
            try {
                runningThreadsNum.acquire();
            } catch (InterruptedException e) {
                throw new RuntimeException();
            }
            for (int i = 0; i < SynchronizedVSLock.loopTime; i++) {
                SynchronizedVSLock.lock.lock();
                System.out.println(SynchronizedVSLock.random.nextDouble());
                SynchronizedVSLock.lock.unlock();
            }
            runningThreadsNum.release();
        }
    }

    static class Test2 implements Runnable {
        @Override
        public void run() {
            try {
                runningThreadsNum.acquire();
            } catch (InterruptedException e) {
                throw new RuntimeException();
            }
            for (int i = 0; i < SynchronizedVSLock.loopTime; i++) {
                synchronized (SynchronizedVSLock.lock) {
                    System.out.println(SynchronizedVSLock.random.nextDouble());
                }
            }
            runningThreadsNum.release();
        }
    }
}

The general idea is to create several threads to concurrently execute the task of outputting random numbers, respectively using the two synchronization mechanisms of lock and synchronized. Finally, the program running time is output as an indicator. Use semaphore to ensure that the Main thread doesn't get the final elapsed time until all the children have finished But I've found that often the Main thread gets all the permissions before the other child threads run, and then prints a very small run time, just a millisecond or two, and I don't know what's wrong.


Solution

  • You're joining a thread that you're not starting, not waiting for any of the threads that you started in your loop:

    for (int i = 0; i < numOfThread - 1; i++) {
       new Thread(new Test1()).start();
    }
    new Thread(new Test1()).join();
    

    Calling join() on an unstarted thread returns immediately. This means that your initial thread can acquire all 6 permits for the semaphore before the first Test1 thread even begins execution, which means it will simply print the time and exit.

    You should consider using a CountDownLatch instead of a sempahore here, as this is the textbook use case for it.