【问题标题】:How to prevent deadlocks in synchronized methods?如何防止同步方法中的死锁?
【发布时间】:2019-08-14 04:10:53
【问题描述】:

在下面的代码中,有可能进入类似于这个问题“Deadlocks and Synchronized methods”的死锁,现在我明白为什么两个线程正在进入 死锁,但是当我执行代码时,线程总是进入死锁,所以:

1 - 此代码中何时不可能出现死锁?

2 - 如何防止它发生?

我尝试像这样使用 wait() 和 notifyAll() :

wait()
waver.waveBack(this)

然后在 waveBack() 中调用 notifyAll(),但它不起作用我错过了什么或误解了什么?

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

}

【问题讨论】:

  • 总是以相同的顺序获取锁。
  • 只有在您尝试等待某些特定条件时才能使用wait,该条件受您在等待时释放的锁的保护。你调用notifyAll 来提醒其他线程条件可能已经改变,但如果条件实际上没有改变,这将毫无用处。
  • .wait() 和 notify() 是相当低级的工具,很难正确使用。查看java.util.concurrent 中的任何工具,例如Semaphore 是否有帮助;他们更容易使用。 Effective Java Item 69 更详细地介绍了如何安全使用wait() 和notify()。

标签: java multithreading methods deadlock synchronized


【解决方案1】:

在这种情况下,不要在持有锁的同时调用其他可能需要锁的方法。这确保了一个方法总能在某个时刻获得锁定并取得进展。

在waver.waveBack(this) 之前调用wait() 会导致鸡与蛋问题:永远不会调用waveBack(this),因为线程在wait() 语句处停止执行,因此永远不会调用notifyAll() 以继续执行。

在示例的上下文中,有多种方法可以防止死锁,但让我们从您链接的问题中的answer 中的一个 cmets 中接受 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();
        }
    }

}

【讨论】:

  • 我对 wait() 和 notifyAll() 有错误的想法,尽管这是考试的一部分,但我是应该在保留程序语义的同时防止死锁,并且我不允许更改 main() 方法。
  • @proless8 好吧,希望我的例子能给你一些想法。无论如何,在您的原始示例中,当没有明显的理由使用“同步”方法时,很难猜测“程序的语义”。但我想这就是为什么它是学术性的:-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多