Search code examples
javamultithreadingconditional-statementsreentrantlock

Communication between threads in Java without lock


So what i want to achieve is two threads taking turn in executing their task. I originally had just one question;

  1. How can i achieve two threads taking turns in executing their task without using a lock? Reason why i don't want the lock is because I'm feeling stupid, using a lock when theres no common resource the two threads are trying to access.

So I was going to make a small example of the code, and then i discovered that i wasn't able to make it work, even with locks. So my second question is; how do i make the code work as expected? The way i see it, it should work, but that's just me :)

  1. Thread1 prints message
  2. Thread1 signals that Thread2 can print message
  3. Thread2 prints message
  4. Thread2 signals that Thread1 can start all over

public class App {

    Lock lock = new ReentrantLock();
    Condition cond1 = lock.newCondition();
    Condition cond2 = lock.newCondition();

    public App() {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (true) {
                        lock.lock();
                        System.out.println("Thread 1");
                        cond2.signalAll();
                        cond1.await();
                        lock.unlock();
                    }
                } catch (InterruptedException e) {
                }
            }
        });
        thread1.start();

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (true) {
                        lock.lock();
                        cond2.await();
                        System.out.println("           Thread 2");
                        cond1.signalAll();
                        lock.unlock();
                    }
                } catch (InterruptedException e) {
                }
            }
        });
        thread2.start();
    }

    public static void main(String[] args) {
        new App();
    }

}

Solution

  • (1) await() is usually used in a loop; not doing so is a sign of bug.

    while( some condition not met )
        cond.await();
    

    (2) unlock() should be in a finally block

    (3) signal() only signals currently waiting threads; the signal is lost if there's no thread waiting.

        lock.lock();
        cond.signal(); // lost
        cond.await();  // won't wake up
    

    (4) nothing wrong to use good old synchronized for simple cases like this. Actually you better understand it first before using more "advanced" stuff.

    (5) a solution:

        Lock lock = new ReentrantLock();
        Condition cond = lock.newCondition();
        int turn=1; // 1 or 2
    
    
        // thread1
    
                lock.lock();
                try
                {
                    while (true)
                    {
                        while(turn!=1)
                            cond.await();
                        System.out.println("Thread 1");
                        turn=2;
                        cond.signal();
                    }
                }
                finally
                {
                    lock.unlock();
                }
    
        // thread2
        // switch `1` and `2`
    

    (6) a ring of threads, each waking up the next

        int N = 9;
        Thread[] ring = new Thread[N];
        for(int i=0; i<N; i++)
        {
            final int ii = i+1;
            ring[i] = new Thread(()->
            {
                while(true)
                {
                    LockSupport.park();   // suspend this thread
                    System.out.printf("%"+ii+"d%n", ii);
                    LockSupport.unpark(ring[ii%N]);  // wake up next thread
                    // bug: spurious wakeup not handled 
                }
            });
        }
    
        for(Thread thread : ring)
            thread.start();
    
        LockSupport.unpark(ring[0]);  // wake up 1st thread