1

In the following Code there is a potential to enter a Deadlock similar to this Question "Deadlocks and Synchronized methods", now i understand why the two Threads are entering a deadlock, but when i execute the code the Threads always enters a Deadlock so:

1 - When is a Deadlock not possible in this code ?

2 - How to prevent it from happening ?

I tried using wait() and notifyAll() like this :

wait()
waver.waveBack(this)

and then calling notifyAll() in waveBack(), but it didn't work what am i missing or misunderstood ?

package mainApp;

public class Wave {

    static class Friend {

        private final String name;

        public Friend(String name) {
            this.name = name;
        }

        public String getName() {
            return this.name;
        }

        public synchronized void wave(Friend waver) {
            String tmpname = waver.getName();
            System.out.printf("%s : %s has waved to me!%n", this.name, tmpname);
            waver.waveBack(this);
        }

        public synchronized void waveBack(Friend waver) {
            String tmpname = waver.getName();
            System.out.printf("%s : %s has waved back to me!%n", this.name, tmpname);
        }
    }

    public static void main(String[] args) {
        final Friend friendA = new Friend("FriendA");
        final Friend friendB = new Friend("FriendB");
        new Thread(new Runnable() {
            public void run() {
                friendA.wave(friendB);
            }
        }).start();
        new Thread(new Runnable() {
            public void run() {
                friendB.wave(friendA);
            }
        }).start();
    }

}
proless8
  • 57
  • 1
  • 9
  • Always acquire the locks in the same order. – user207421 Mar 23 '19 at 09:48
  • You can only use `wait` when there's some specific condition you are trying to wait for that is protected by the lock you release while waiting. You call `notifyAll` to alert other threads that the condition may have changed, but that serves no purpose if no condition has actually been changed. – David Schwartz Mar 23 '19 at 18:57
  • `.wait()` and `notify()` are pretty low-level tools and hard to use right. See if any of the tools in [`java.util.concurrent`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/package-summary.html) such as `Semaphore` can help; they're easier to work with. Effective Java Item 69 goes into more detail about how to use `wait()` and `notify()` safely. – dimo414 Mar 23 '19 at 18:58

1 Answers1

1

In this case, simply do not call another method that might need the lock while holding the lock. This ensures that there is always a moment in time where a method can get the lock and progress can be made.

Calling wait() before waver.waveBack(this) causes a chicken and egg problem: waveBack(this) is never called because the thread stops execution at the wait() statement and thus notifyAll() is never called to continue execution.

There are various ways to prevent deadlocks in the context of the example, but let's go with the advice from sarnold in one of the comments in his answer from the question you linked. To paraphrase sarnold: "it is usually easier to reason about locks on data".

Let's assume that the synchronized methods are synchronized to ensure some consistent update of state (i.e. some variables need to be updated but only one thread at any given time can modify these variables). For example, let's register the amount of waves send and waves received. The runnable code below should demonstrate this:

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Wave {

    static class Waves {

        final Map<Friend, Integer> send = new HashMap<>();
        final Map<Friend, Integer> received = new HashMap<>();

        void addSend(Friend f) {
            add(f, send);
        }
        void addReceived(Friend f) {
            add(f, received);
        }
        void add(Friend f, Map<Friend, Integer> m) {
            m.merge(f, 1, (i, j) -> i + j);
        }
    }

    static class Friend {

        final String name;

        public Friend(String name) {
            this.name = name;
        }

        final Waves waves = new Waves();

        void wave(Friend friend) {

            if (friend == this) {
                return; // can't wave to self.
            }
            synchronized(waves) {
                waves.addSend(friend);
            }
            friend.waveBack(this); // outside of synchronized block to prevent deadlock
        }

        void waveBack(Friend friend) {

            synchronized(waves) {
                waves.addReceived(friend);
            }
        }

        String waves(boolean send) {

            synchronized(waves) {
                Map<Friend, Integer> m = (send ? waves.send : waves.received);
                return m.keySet().stream().map(f -> f.name + " : " + m.get(f))
                        .sorted().collect(Collectors.toList()).toString();
            }
        }

        @Override
        public String toString() {
            return name + ": " + waves(true) + " / " + waves(false);
        }
    }

    final static int maxThreads = 4;
    final static int maxFriends = 4;
    final static int maxWaves = 50_000;

    public static void main(String[] args) {

        try {
            List<Friend> friends = IntStream.range(0, maxFriends)
                    .mapToObj(i -> new Friend("F_" + i)).collect(Collectors.toList());
            ExecutorService executor = Executors.newFixedThreadPool(maxThreads);
            Random random = new Random();
            List<Future<?>> requests = IntStream.range(0, maxWaves)
                    .mapToObj(i -> executor.submit(() -> 
                        friends.get(random.nextInt(maxFriends))
                            .wave(friends.get(random.nextInt(maxFriends)))
                        )
                    ).collect(Collectors.toList());
            requests.stream().forEach(f -> 
                { try { f.get(); } catch (Exception e) { e.printStackTrace(); } }
            );
            executor.shutdownNow();
            System.out.println("Friend: waves send / waves received");
            friends.stream().forEachOrdered(p -> System.out.println(p));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
vanOekel
  • 6,358
  • 1
  • 21
  • 56
  • i had the wrong idea about the **wait()** and **notifyAll()** thanks for the clarification although this was part of an exam and i am supposed to prevent deadlocks while preserving the semantik of the program and i am not allowed to change the **main()** method. – proless8 Mar 23 '19 at 19:47
  • 1
    @proless8 ah ok, hopefully my example gives you some ideas. Anyway, "the semantik of the program" is difficult to guess when, in your original example, there is no apparent reason to use the "synchronized" methods. But I guess that is why it is academic :-) – vanOekel Mar 23 '19 at 21:03