java避免死锁

时间:2016-10-10 15:42:01

标签: java multithreading

我们的应用程序(使用java)非常复杂,我有以下示例棘手的代码,将在多线程环境中使用。

现在问题是当我运行下面的代码(以独立主程序运行它)时,我遇到死锁。

地点类: -

public class Location implements Runnable {

    private final int id;

    private final int[] dependentLocationIds;

    private final Lock lock = new ReentrantLock();

    public Location(int id, int[] dependentLocationIds) {
        this.id = id;
        this.dependentLocationIds = dependentLocationIds;
    }

    public int getId() {
        return id;
    }

    public boolean blockLocation() {
        lock.lock();
        return true;
    }

    public boolean releaseLocation() {
        lock.unlock();
        return true;
    }

    public boolean occupy() {
        boolean occupationStatus = false;
        //order ids first
        Arrays.sort(dependentLocationIds);

        lock.lock();
        try {

            //below sleep temporarily added to track the progress slowly
            Thread.sleep(1000);

            //Check dependentLocations are NOT being modified concurrently
            for(int id : dependentLocationIds) {
                Location location = LocationHelper.getLocation(id);
                System.out.println(Thread.currentThread().getName()+": blocking required dependent location :"+id);
                location.blockLocation();
            }
            //the above blocked Locations will be released in leave()

            //complex business logic to check and then make occupationStatus to true
            occupationStatus = true;
            System.out.println(id + ": location occupied by:"+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if(!occupationStatus) {
                lock.unlock();
            }
           //if occupationStatus is true, the lock will be released in leave()
        }
        return occupationStatus;
    }

    public boolean leave() {
        boolean leaveStatus = false;
        //order ids first
        Arrays.sort(dependentLocationIds);
        try {
            //below sleep temporarily added to track the progress slowly
            Thread.sleep(1000);

            //complex business logic to check and then make leaveStatus to true
            leaveStatus = true;

            //now release dependent locations in reverse order
            for(int i=dependentLocationIds.length; i>0;i--) {
                Location location = LocationHelper.getLocation(id);
                System.out.println(Thread.currentThread().getName()+": releasing required dependent location :"+id);
                location.releaseLocation();
            }

            System.out.println(id + ": location released by "+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return leaveStatus;
    }

    public void run() {
        occupy();
        //some business logic goes here
        leave();
    }

    public static void main(String[] args) {
        List<Location> locations = LocationHelper.getLocations();

        for(Location location : locations) {
            //Each location runs in different threads here
            new Thread(location, "THREAD-"+location.getId()).start();
        }
    }
}

LocationHelper类: -

public class LocationHelper {

    private static final List<Location> locations = new ArrayList<>();

    static {
        int[] locationids1 = {2, 3, 4, 5};
        Location location1 = new Location(1, locationids1);
        locations.add(location1);

        int[] locationids2 = {1, 3, 4};
        Location location2 = new Location(2, locationids2);
        locations.add(location2);

        int[] locationids3 = {1, 2, 4};
        Location location3 = new Location(3, locationids3);
        locations.add(location3);

        int[] locationids4 = {3, 5};
        Location location4 = new Location(4, locationids4);
        locations.add(location4);

        int[] locationids5 = {1, 2, 3, 4};
        Location location5 = new Location(5, locationids5);
        locations.add(location5);
    }

    public static List<Location> getLocations() {
        return locations;
    }

    public static Location getLocation(int id) {
        Location required = null;

        for(Location location : locations) {
            if(location.getId() == id) {
                required = location;
            }
        }
        return required;
    }
}
  

核心要求是我更新特定的“位置”时   对象,应该允许依赖的“位置”对象的NONE   更改。所以我也试图锁定依赖对象,这就是复杂性出现的地方。

我尝试根据'location id'(唯一)对位置对象进行排序,然后锁定位置对象,以避免死锁,但没有运气。

请问如何修改此代码以避免死锁?

如何重构“位置”类以消除上述复杂性? 或者,“位置”类是否还有其他更好的设计选项(使用并发api)来简化上述逻辑?请帮忙。

4 个答案:

答案 0 :(得分:5)

  

问题是当我运行下面的代码(以独立主程序运行它)时,我遇到死锁。

我并不感到惊讶。事实上,我无法想象您希望代码如何工作。你启动了一堆线程,每个线程都试图锁定系统中五个Location个实例中的几个。死锁所需要的只是两个线程,每个线程锁定另一个要锁定的位置。

例如,第一个线程通过锁定位置1开始,并且它尝试锁定的其他位置是位置2.第二个线程通过锁定位置2开始,而其他尝试锁定的位置是位置1。每个线程在尝试获取其第二个锁之前成功获取其第一个锁,然后你就是吐司,并且在你的程序中存在如此多的这样的死锁机会,程序很可能不会死锁。

最简单的解决方案是避免尝试并行执行所有这些锁定。当然,根本不需要锁定。或者,对于此特定代码(但可能不是更大的代码),您可以使用ReentrantReadWriteLock的读取端,这允许多个线程一次获取它。另一方面,如果没有人获得相关的写锁定,这又等同于根本没有锁定。

订购锁定获取也应解决您的问题,但这样做似乎与问题不相容。具体来说,我观察到每个Location首先锁定本身,但随后可能还需要锁定其他位置的任意组合,包括那些先前排序的位置。假设第一次锁定是必不可少的(即您不能将其作为后续锁定序列的一部分执行)并且您不能限制Locations仅锁定具有更大ID的其他位置,我真的看不到方法安全地执行此操作,而不是序列化Location的运行。

答案 1 :(得分:1)

我做了一些重新排列,但是在调试语句之后,看起来这确实是你试图完成的。我为测试编写了一些代码,并改为Lists而不是数组。

位置等级。

devtools::build_vignettes

LocationHelper Class

import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Location
    implements Runnable
{

  private final int id;

  private final List<Integer> dependentLocationIds;

  private final Lock lock = new ReentrantLock();

  private boolean isUnlocked = true;

  public Location(int id, List<Integer> dependentLocationIds)
  {
    this.id = id;
    this.dependentLocationIds = dependentLocationIds;
    Collections.sort(dependentLocationIds);
  }

  public int getId()
  {
    return id;
  }

  public List<Integer> getDependentLocationIds()
  {
    return dependentLocationIds;
  }

  public boolean isUnlocked()
  {
    return isUnlocked;
  }

  public boolean blockLocation()
  {
    lock.lock();
    isUnlocked = false;
    System.out.printf("Location: %d occupied by: %s\n", this.getId(),
        Thread.currentThread().getName());
    return isUnlocked;
  }

  public boolean releaseLocation()
  {
    lock.unlock();
    isUnlocked = true;
    System.out.printf("Location: %d released by: %s\n", this.getId(),
        Thread.currentThread().getName());
    return isUnlocked;
  }

  public void occupy()
  {
    while (!LocationHelper.acquireLocks(this))
    {
      try
      {
        System.out.printf("Location: %d sleeping during occupy on: %s\n",
            this.getId(), Thread.currentThread().getName());
        Thread.sleep(1500);
      }
      catch (InterruptedException e)
      {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
    try
    {
      // below sleep added to track the progress slowly
      Thread.sleep(1000);

      System.out.printf("Location: %d doing something on: %s\n", this.getId(),
          Thread.currentThread().getName());
    }
    catch (InterruptedException e)
    {
      e.printStackTrace();
      LocationHelper.releaseLocks(this);
    }
  }

  public void leave()
  {
    try
    {
      // below sleep added to track the progress slowly
      Thread.sleep(1000);

      System.out.printf("Location: %d is attempting to leave on: %s\n",
          this.getId(), Thread.currentThread().getName());
      LocationHelper.releaseLocks(this);
    }
    catch (InterruptedException e)
    {
      e.printStackTrace();
    }
    finally
    {
      LocationHelper.releaseLocks(this);
    }
  }

  public void run()
  {
    occupy();
    leave();
  }

  public static void main(String[] args)
  {
    List<Location> locations = LocationHelper.getLocations();

    for (Location location : locations)
    {
      // Each location runs in different threads here
      new Thread(location, "THREAD-" + location.getId()).start();
    }
  }
}

答案 2 :(得分:0)

订购的想法看起来不错,订购必须解决de deadlock问题。如果您总是以相同的顺序获得锁,那么您将是安全的。

然而,我注意到您只订购直接家属。您应该在开始锁定之前订购所有依赖项递归。而且自己的实例也应该将自己放在要排序的列表中。

这样您就可以安全地锁定每个位置的所有“依赖关系树”。

仅用于说明,请查看以下示例。

public List<Integer> getDependentTreeLocationIds() {
    List<Integer> ret = new ArrayList<Integer>();
    getLocationIdsRecursively(ret);
    return ret;
}

private void getLocationIdsRecursively(List<Integer> foundLocations) {
    if (foundLocations.contains(this.id) {
        // avoid infinite loop in case of circular dependencies
        return;
    }
    foundLocations.add(this.id);
    for (int id : dependentLocationIds) {
        Location location = LocationHelper.getLocation(id);
        location.getLocationIdsRecursively(foundLocations);
    }
}

另外,请考虑Bollinger关于算法选择的答案。如果您的依赖树与位置总数相比相对较小,则可能是一个有效的选择。但请注意,从现在开始,这将是一条漫长的道路。

答案 3 :(得分:0)

所以你有两种模式似乎有问题。

首先,希望最简单,

public boolean occupy() {
    boolean occupationStatus = false;
    //order ids first
    Arrays.sort(dependentLocationIds);

    lock.lock();
    try {
        //bunch of stuff
    } finally {
        if(!occupationStatus) {
            lock.unlock();
        }
    }
    return occupationStatus;
}

始终锁定lock但不总是解锁它很奇怪。这意味着锁定用法未被封装,这可能会产生意外或难以预测的副作用。你应该总是以你得到它们的相反顺序释放锁,把它们想象成堆栈。

其次,你是嵌套锁。如果你有多个线程遍历这个,你需要考虑持有和尝试获取锁的每个排列。这非常复杂。

我建议设置主锁(静态或单独),并为每个操作锁定,然后锁定获取可以使用tryLock,如果锁定则会快速失败。例如:

public boolean occupy() {
    boolean occupationStatus = false;
    masterLock.lock();
    try{
        //order ids first
        Arrays.sort(dependentLocationIds);

        if(lock.tryLock()){
            //handle already blocked
        }
        try {
            //bunch of stuff
        } finally {
            if(!occupationStatus) {
                lock.unlock();
            }
        }
    }finally{
        masterLock.unlock();
    }
    return occupationStatus;
}

occupy的调用进行排序也很奇怪,我会在构造函数中执行此操作。