Как предотвратить взаимные блокировки в синхронизированных методах? - PullRequest
1 голос
/ 23 марта 2019

В следующем коде есть потенциальная возможность ввести тупик, похожий на этот вопрос " Взаимоблокировки и синхронизированные методы ", теперь я понимаю, почему два потока входят в тупик, но когда я выполняю код, потоки всегда входят в тупик так:

1 - Когда в этом коде невозможен тупик?

2 - Как предотвратить это?

Я пытался использовать wait () и notifyAll () , например:

wait()
waver.waveBack(this)

и затем вызов notifyAll () in waveBack () , но это не сработало, что я пропустил или неправильно понял?

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();
    }

}

1 Ответ

1 голос
/ 23 марта 2019

В этом случае просто не вызывайте другой метод, который может нуждаться в блокировке, удерживая блокировку. Это гарантирует, что всегда есть момент времени, когда метод может получить блокировку и прогресс может быть достигнут.

Вызов wait() до waver.waveBack(this) вызывает проблему с курицей и яйцом: waveBack(this) никогда не вызывается, потому что поток останавливает выполнение в операторе wait() и, следовательно, notifyAll() никогда не вызывается для продолжения выполнения.

Существуют различные способы предотвращения взаимных блокировок в контексте примера, но давайте обратимся к совету sarnold в одном из комментариев в его ответе на вопрос, который вы связали. Перефразируя sarnold: «обычно легче рассуждать о блокировке данных».

Предположим, что синхронизированные методы синхронизированы для обеспечения некоторого согласованного обновления состояния (то есть некоторые переменные должны быть обновлены, но только один поток в любой момент времени может изменять эти переменные). Например, давайте зарегистрируем количество отправленных и полученных волн. Выполненный ниже код должен продемонстрировать это:

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();
        }
    }

}
...