如何防止同步方法中的死锁?
How to prevent deadlocks in synchronized methods?
在下面的代码中有可能进入类似于这个问题“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();
}
}
在这种情况下,只要在持有锁的同时不调用其他可能需要锁的方法即可。这确保了总是有一个方法可以获取锁并可以取得进展的时刻。
在 waver.waveBack(this)
之前调用 wait()
会导致先有鸡还是先有蛋的问题:waveBack(this)
永远不会被调用,因为线程在 wait()
语句处停止执行,因此 notifyAll()
永远不会调用继续执行。
在示例的上下文中有多种方法可以防止死锁,但让我们按照 sarnold
在您链接的问题 answer 的评论之一中提出的建议。换句话说 sarnold
:"it is usually easier to reason about locks on data".
让我们假设同步方法是同步的,以确保状态更新的一致性(即一些变量需要更新,但在任何给定时间只有一个线程可以修改这些变量)。例如,让我们记录发送的波数和接收的波数。下面的可运行代码应该证明这一点:
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();
}
}
}
在下面的代码中有可能进入类似于这个问题“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();
}
}
在这种情况下,只要在持有锁的同时不调用其他可能需要锁的方法即可。这确保了总是有一个方法可以获取锁并可以取得进展的时刻。
在 waver.waveBack(this)
之前调用 wait()
会导致先有鸡还是先有蛋的问题:waveBack(this)
永远不会被调用,因为线程在 wait()
语句处停止执行,因此 notifyAll()
永远不会调用继续执行。
在示例的上下文中有多种方法可以防止死锁,但让我们按照 sarnold
在您链接的问题 answer 的评论之一中提出的建议。换句话说 sarnold
:"it is usually easier to reason about locks on data".
让我们假设同步方法是同步的,以确保状态更新的一致性(即一些变量需要更新,但在任何给定时间只有一个线程可以修改这些变量)。例如,让我们记录发送的波数和接收的波数。下面的可运行代码应该证明这一点:
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();
}
}
}