
通常来说死锁,要遇到死锁问题死锁,必须满足以下条件:
相互排斥的条件: 资源一次只能由一个线程使用. 请求和保留条件: 当线程由于请求资源而被阻塞时,它将保留获取的资源. 无剥夺条件: 线程所获取的资源在用尽之前不能强行剥夺. 循环等待条件: 多个线程之间形成了线程等待资源关系.
在JAVA编程中,有三种典型的死锁类型:

静态锁序列死锁,动态锁序列死锁以及协作对象之间发生的死锁.
A和b两种方法都需要获得A锁和B锁. 一个线程执行方法a,并已获取A锁,正在等待B锁;另一个线程执行方法b,并已获取B锁,正在等待A锁. 在这种状态下,将发生静态锁定序列死锁.
//可能发生静态锁顺序死锁的代码
class StaticLockOrderDeadLock {
private final Object lockA = new Object();
private final Object lockB = new Object();
public void a() {
synchronized (lockA) {
synchronized (lockB) {
System.out.println("function a");
}
}
}
public void b() {
synchronized (lockB) {
synchronized (lockA) {
System.out.println("function b");
}
}
}
}

解决静态锁序列死锁的方法是: 所有需要多个锁的线程必须以相同的顺序获取锁.
//正确的代码
class StaticLockOrderDeadLock {
private final Object lockA = new Object();
private final Object lockB = new Object();
public void a() {
synchronized (lockA) {
synchronized (lockB) {
System.out.println("function a");
}
}
}
public void b() {
synchronized (lockA) {
synchronized (lockB) {
System.out.println("function b");
}
}
}
}
动态锁定序列死锁是指由两个线程调用相同方法时传递的反向参数引起的死锁. 在下面的代码中,一个线程调用transferMoney方法,并传入参数accountA和accountB. 另一个线程调用transferMoney方法,并传入参数accountB和accountA. 这时,可能会发生静态锁定序列死锁的问题,即: 第一个线程获取accountA锁并等待accountB锁,第二个线程获取accountB锁并等待accountA锁.

//可能发生动态锁顺序死锁的代码
class DynamicLockOrderDeadLock {
public void transefMoney(Account fromAccount, Account toAccount, Double amount) {
synchronized (fromAccount) {
synchronized (toAccount) {
//...
fromAccount.minus(amount);
toAccount.add(amount);
//...
}
}
}
}
动态锁定序列死锁解决方案如下: 使用System.identifyHashCode定义锁定序列. 确保所有线程以相同顺序获取锁.
//正确的代码
class DynamicLockOrderDeadLock {
private final Object myLock = new Object();
public void transefMoney(final Account fromAccount, final Account toAccount, final Double amount) {
class Helper {
public void transfer() {
//...
fromAccount.minus(amount);
toAccount.add(amount);
//...
}
}
int fromHash = System.identityHashCode(fromAccount);
int toHash = System.identityHashCode(toAccount);
if (fromHash < toHash) {
synchronized (fromAccount) {
synchronized (toAccount) {
new Helper().transfer();
}
}
} else if (fromHash > toHash) {
synchronized (toAccount) {
synchronized (fromAccount) {
new Helper().transfer();
}
}
} else {
synchronized (myLock) {
synchronized (fromAccount) {
synchronized (toAccount) {
new Helper().transfer();
}
}
}
}
}
}

有时,死锁不是那么明显,例如两个协作类之间的死锁,例如以下代码: 一个线程调用Taxi对象的setLocation方法,另一个线程调用Dispatcher对象方法的getImage. . 可能发生第一个线程持有Taxi对象锁并等待Dispatcher对象锁,而另一个线程持有Dispatcher对象锁并等待Taxi对象锁.
//可能发生死锁
class Taxi {
private Point location, destination;
private final Dispatcher dispatcher;
public Taxi(Dispatcher dispatcher) {
this.dispatcher = dispatcher;
}
public synchronized Point getLocation() {
return location;
}
public synchronized void setLocation(Point location) {
this.location = location;
if (location.equals(destination))
dispatcher.notifyAvailable(this);//外部调用方法,可能等待Dispatcher对象锁
}
}
class Dispatcher {
private final Set<Taxi> taxis;
private final Set<Taxi> availableTaxis;
public Dispatcher() {
taxis = new HashSet<Taxi>();
availableTaxis = new HashSet<Taxi>();
}
public synchronized void notifyAvailable(Taxi taxi) {
availableTaxis.add(taxi);
}
public synchronized Image getImage() {
Image image = new Image();
for (Taxi t : taxis)
image.drawMarker(t.getLocation());//外部调用方法,可能等待Taxi对象锁
return image;
}
}
在上面的代码中,我们在持有锁的同时调用了外部方法,这非常危险(可能会发生死锁). 为了避免这种危险情况,我们使用公开通话. 如果在调用外部方法时不需要保持锁定,则将其称为打开调用.
解决协作对象之间的死锁: 需要使用打开调用,这是为了避免在持有锁的情况下调用外部方法.
//正确的代码
class Taxi {
private Point location, destination;
private final Dispatcher dispatcher;
public Taxi(Dispatcher dispatcher) {
this.dispatcher = dispatcher;
}
public synchronized Point getLocation() {
return location;
}
public void setLocation(Point location) {
boolean flag = false;
synchronized (this) {
this.location = location;
flag = location.equals(destination);
}
if (flag)
dispatcher.notifyAvailable(this);//使用开放调用
}
}
class Dispatcher {
private final Set<Taxi> taxis;
private final Set<Taxi> availableTaxis;
public Dispatcher() {
taxis = new HashSet<Taxi>();
availableTaxis = new HashSet<Taxi>();
}
public synchronized void notifyAvailable(Taxi taxi) {
availableTaxis.add(taxi);
}
public Image getImage() {
Set<Taxi> copy;
synchronized (this) {
copy = new HashSet<Taxi>(taxis);
}
Image image = new Image();
for (Taxi t : copy)
image.drawMarker(t.getLocation());//使用开放调用
return image;
}
}
总的来说,有三种常见的死锁类型. 即: 静态锁序列死锁,动态锁序列死锁,协作对象之间的死锁. 编写代码时,请确保线程在获取多个锁时使用一致的顺序. 同时,避免在持有锁的同时调用外部方法.
本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/jisuanjixue/article-249199-1.html
一吨大豆出油率17---22%左右
绝不手软
“台湾区渔业广播电台”报了三十几年钓鱼台的天气