3

Say we create a thread which runs a synchronized method. This method tries to take() from an empty blocking queue. Now let a separate thread then try to put() and element onto the blocking queue while synchronized on the same object.

This causes a deadlock:

  • The first thread will not release the lock until an element is added to the queue.
  • The second thread cannot add an element until the lock is free for it to acquire.

If the two actions need to be atomic and run on separate threads, how can this be achieved without causing a deadlock?

I understand that take() and put() are thread-safe. My question is for when they are used as part of larger actions that must be atomic.

Example:

import java.util.concurrent.*;

public class DeadlockTest {

    String input = "Nothing added yet!";
    LinkedBlockingQueue<String> buffer = new LinkedBlockingQueue<>();

    public synchronized String getFromBuffer() {
        System.out.println("Trying to get input from buffer.");
        try {
            input = buffer.take();
        } catch (InterruptedException ex) {}
        System.out.println("Got:" + input + "\n");
        return input;
    }

    public static void main(String[] args) throws InterruptedException {
        DeadlockTest dl = new DeadlockTest();

        new Thread(() -> {
            dl.getFromBuffer();
        }).start();

        // Give new thread time to run.
        Thread.sleep(500);

        synchronized (dl) {
            String message = "Hello, world!";

            System.out.println("Adding: " + message);
            dl.buffer.put(message);
            System.out.println("Added!\n");

            System.out.println("Message: " + dl.input);
        }
    }
}
nolanar
  • 33
  • 4
  • Well, yes, so take/pop/whatever must release the lock even if the queue is empty. – Martin James Oct 31 '15 at 14:54
  • Usually, you use extra thread to execute blocking stuff and then read results using some event loop. – Tomáš Zato Oct 31 '15 at 14:59
  • @MartinJames The lock object that `take()` and `put()` acquire and release is not the same one that the synchronized method uses. I do not have access to the lock internal to the blocking queue. If I did I could use it as the argument to a synchronized block in place of making the method synchronized. – nolanar Oct 31 '15 at 15:24

1 Answers1

3

Say we create a thread which runs a synchronized method. This method tries to take() from an empty blocking queue.

Sounds like bad design. It's usually a mistake to call any blocking methods from within a synchronized method or a synchronized statement.

If the two actions need to be atomic and run on separate threads, how can this be achieved without causing a deadlock?

Well, there's two possibilities:

In one case, the two threads are acting on different data. In that case, they should be using different locks, and they won't interfere with one another at all.

In the other case, the two threads are acting on the same data. In that case, they should lock the same lock, and one thread will have to wait for the other.


Maybe you misunderstand how a blocking queue works. If one thread is waiting to take() something from a blocking queue, that should never prevent another thread from calling put(). That would be the exact opposite of what you want.

What you want (and what you'll get from any of the blocking queue implementations in the Java standard library) is that the put() operation in the second thread will wake up the thread that's waiting to take() something from the queue.

Solomon Slow
  • 25,130
  • 5
  • 37
  • 57
  • Thanks for the feedback James! Poor design is certainly the problem. That's why I am asking how I can achieve a similar effect with a different design. My understanding is that 'take()' does indeed release a lock when it blocks. However this is not the same lock object that the synchronized method uses. If I had access to the lock object internal to the blocking queue then I could use that as the argument to a synchronized block. That is probably also poor design though! – nolanar Oct 31 '15 at 15:18
  • The primitive feature that you want is the `wait()/notify()` mechanism. Have a look at the `produceSomething()` and `consumeSomething()` methods in my answer to a different question. http://stackoverflow.com/questions/26590542/java-lang-illegalmonitorstateexception-object-not-locked-by-thread-before-wait/26593239#26593239 The `lock.wait()` call is only allowed inside a `synchronized(lock)` statement. It temporarily unlocks the lock, it waits to be notified by the other thread, and then it re-locks the lock before returning. – Solomon Slow Nov 02 '15 at 14:19