Java并发之死锁
程序员文章站
2022-04-18 11:31:01
...
前言
在Java编程中,有3种典型的死锁类型:静态的锁顺序死锁、动态的锁顺序死锁、协作对象之间的死锁
一、静态的锁顺序死锁
a、b两个方法都要获得A锁和B锁。线程1执行a方法获得了A锁,在等待B锁;线程2执行了b方法获得了B锁,在等待A锁。
/**
*可能会发生静态的锁顺序死锁
*/
public class StaticLockOrderDeadLock {
private final Object lockA = new Object();
private final Object lockB = new Object();
private void a(){
synchronized (lockA){
synchronized (lockB){
System.out.println("方法a");
}
}
}
private void b(){
synchronized (lockB){
synchronized (lockA){
System.out.println("方法b");
}
}
}
}
**解决方式:**所有需要多个锁的线程,都要以相同的顺序获得锁。
/**
*这样就能避免了
*/
public class StaticLockOrderDeadLock {
private final Object lockA = new Object();
private final Object lockB = new Object();
private void a(){
synchronized (lockA){
synchronized (lockB){
System.out.println("方法a");
}
}
}
private void b(){
synchronized (lockA){
synchronized (lockB){
System.out.println("方法b");
}
}
}
}
二、动态的锁顺序死锁
两个线程调用同一个方法时,传入的参数颠倒造成的死锁
//两个线程调用此方法,参数传反就会发生动态的锁顺序死锁
//线程1获得了accountA锁,等待accountB锁。线程2获得了accountB锁,等待accountA锁
public class DynamicLockOrderDeadLock {
public void transefMoney(Account fromAccount, Account toAccount,Double amount){
synchronized (fromAccount){
synchronized (toAccount){
...
fromAccount.minus(amount);
toAccount.add(amount);
...
}
}
}
}
使用System.identityHashCode来定义锁的顺序,确保所有线程都能以相同的方式获得锁
public class DynamicLockOrderDeadLock {
private final Object myLock = new Object();
public void transefMoney(Account fromAccount, Account toAccount,Double amount){
class Helper{
private 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对象的getImg()。有可能线程1持有Taxi对象锁并等待Dispatcher对象锁,而线程2持有Dispatcher对象锁而等待Taxi对象。
public 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对象锁
}
}
public 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 getImg(){
Image image = new Image();
for (Taxi t:taxis){
image.drawMarker(t.getLocation());//外部调用方法,可能要等待Taxi对象锁
return image;
}
}
}
在持有锁的前提下,就调用了外部方法极易发生死锁。需要作出调整,调用外部方法时就不再需要持有锁,即开放调用。
public 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);//开放调用
}
}
public 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 getImg(){
Set<Taxi> copy;
synchronized (this){
copy = new HashSet<>(taxis);
}
Image image = new Image();
for (Taxi t:copy){
image.drawMarker(t.getLocation());//开放调用
return image;
}
}
}
总结死锁的产生条件
1.互斥条件:一个资源每次只能被一个线程使用
2.请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放
3.不剥夺条件:线程已获得资源,在未使用完之前,不能强行剥夺
4.循环等待条件:若干线程之间形成一种首尾相接循环等待资源关系
上一篇: 并发编程之死锁解析