Search code examples
javamultithreadingdeadlock

Locking many resources without wait & hold in Java


I ran into the problem of having to lock 2 or more resources, which led to a Deadlock when using ReentrantReadWriteLocks, even after having the same locking order everywhere*. I implemented a method that takes Lock Objects, locks them all or rollsback and preempts the current thread:

/**
 * Helper Interface for an AutoClosable lock.
 */
public interface ResourceLock extends AutoCloseable {

    /**
     * Unlocking doesn't throw any checked exception.
     */
    @Override
    void close();
}
public static ResourceLock lockAll(Lock... locks) {
    List<Lock> successful = new ArrayList<>();
    boolean acquired = false;

    for (final Lock lock : locks) {
        acquired = false;
        try {
            acquired = lock.tryLock(500, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (acquired) {
            successful.add(lock);
        } else {
        break;
        }
    }

    if (!acquired) {
        for (Lock lock1 : successful) {
            lock1.unlock();
        }
        // Preempt the thread and try again
        Thread.yield();
        return lockAll(locks);
    }

    return  () -> {
      for (final Lock lock : locks) {
        lock.unlock();
      }
    };
  }

Example usage:

try (ResourceLock ignored = lockAll(currentNode.getLock().writeLock(), newNode.getLock().readLock())) {
          currentNode.updateCounts(newNode);
}

Not too nice to read.

Here are my questions:
- How does one properly preempt Threads in Java?
- Is the use of Thread.yield() okay or would Thread.sleep(1) be more appropriate?
- Is there something more elegant than this? e.g. did I oversee a best practice to do this or sth. in util.concurrency?

*The code shall implement a multi-threaded version of conceptual clustering/Cobweb (Fisher, 1987) recursively. The locking order is always parent, currently visited node, new node. But as the threads may be in different levels of the tree at the same time there is at some point an overlap between child in a higher and parent in a lower tree level that leads to the deadlock.


Solution

  • Is the use of Thread.yield() okay? or would Thread.sleep(1) be more appropriate?

    You should be aware that Thread.yield() is not guaranteed to do anything at all. It's an anachronism from a time when somebody imagined that Java programs might possibly run in a cooperative multitasking environment. Cooperative multitasking still exists, but you won't often find it on systems that are powerful enough to host a JVM.

    The sleep(1) call is guaranteed to yield, but it will impact the program's performance---a millisecond is a long time these days. Whether or not the impact is too great is a question that only you can answer.

    I have seen sleep(0) in Java code, but I don't know whether that is required to behave any differently from yield().


    Is there something more elegant than this?

    Maybe not more elegant, but you can avoid the overhead of locking and unlocking multiple OS mutexes (i.e., Lock objects) by keeping a global Set of tree nodes that are "locked," and using a single, global Lock object to control access to the Set.