代码之家  ›  专栏  ›  技术社区  ›  Vasu

java避免死锁

  •  1
  • Vasu  · 技术社区  · 8 年前

    我们的应用程序(使用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;
        }
    }
    

    核心要求是当我更新特定的“位置”时 对象,不应允许任何依赖的“位置”对象 改变因此,我也试图锁定依赖对象,这就是复杂性产生的原因。

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

    您能帮助我们修改代码以避免死锁吗?

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

    4 回复  |  直到 8 年前
        1
  •  5
  •   John Bollinger    8 年前

    问题是,当我运行下面的代码(将其作为独立主代码运行)时,我遇到了死锁。

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

    例如,第一个线程从锁定位置1开始,它尝试锁定的其他位置是位置2。第二个线程从锁位置2开始,它试图锁定的其他线程是位置1。如果每个线程在尝试获取第二个锁之前成功获取了第一个锁,那幺你就完了,在你的程序中有很多这样的死锁机会,所以程序不太可能死锁。

    ReentrantReadWriteLock ,这允许多个线程同时获取它。另一方面,如果没有人获得相关的写锁,这同样等同于根本没有锁。

    订购锁获取也可以解决您的问题,但这样做似乎与问题不兼容。具体而言,我观察到 地方 它本身 首先,然后可能还需要锁定其他位置的任何组合,包括较早排序的位置。假设第一次锁定是必要的(即,您不能将其作为后续锁定序列的一部分执行),并且您不能限制 Locations 要仅锁定具有更大ID的其他位置,除了序列化 地方 这就是。

        2
  •  1
  •   JynXXedRabbitFoot    8 年前

    我做了一些重新排列,但在调试语句之后,似乎这就是您试图完成的。我删除了一些用于测试的代码,并改为列表而不是数组。

    位置类。

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

    LocationHelper类

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Collectors;
    
    public class LocationHelper
    {
      private static List<Location> holdsLocks = new ArrayList<Location>();
    
      private static final List<Location> locations = new ArrayList<>();
    
      private static int printLocks = 0;
    
      static
      {
        locations.add(new Location(1, Arrays.asList(1, 2, 3, 4, 5)));
        locations.add(new Location(2, Arrays.asList(1, 2, 3, 4)));
        locations.add(new Location(3, Arrays.asList(1, 2, 3, 4)));
        locations.add(new Location(4, Arrays.asList(3, 4, 5)));
        locations.add(new Location(5, Arrays.asList(1, 2, 3, 4, 5)));
      }
    
      public static List<Location> getLocations()
      {
        return locations;
      }
    
      public static Location getLocation(int id)
      {
        return locations.stream().filter(l -> l.getId() == id).findFirst()
            .orElse(null);
      }
    
      public static synchronized boolean acquireLocks(Location location)
      {
        if (printLocks % 5 == 0)
        {
          locations.stream()
              .forEach(l -> System.out.printf("Location: %d status: %s\n",
                  l.getId(), String.valueOf(l.isUnlocked())));
        }
        List<Location> required = location.getDependentLocationIds().stream()
            .map(LocationHelper::getLocation).collect(Collectors.toList());
        // If not available fail to lock.
        if (required.stream().filter(l -> !l.isUnlocked()).count() > 0L)
        {
          return false;
        }
        else
        {
          try
          {
            required.stream().forEach(Location::blockLocation);
            holdsLocks.add(location);
            return true;
          }
          catch (Exception e)
          {
            // TODO Auto-generated catch block
            e.printStackTrace();
            required.stream().forEach(Location::releaseLocation);
            return false;
          }
        }
      }
    
      public static boolean releaseLocks(Location location)
      {
        if (!holdsLocks.contains(location))
        {
          return false;
        }
        else
        {
          List<Location> required = location.getDependentLocationIds().stream()
              .map(LocationHelper::getLocation).collect(Collectors.toList());
    
          try
          {
            required.stream().forEach(Location::releaseLocation);
            holdsLocks.remove(location);
            return true;
          }
          catch (Exception e)
          {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return false;
          }
        }
      }
    }
    
        3
  •  0
  •   Taylor    8 年前

    因此,有两种模式似乎有问题。

    首先,希望是最直接的,

    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 ,我会在构造函数中这样做。

        4
  •  0
  •   DGardim    8 年前

    排序的想法看起来不错,排序必须解决去死锁问题。如果您总是以相同的顺序获取锁,您将是安全的。

    然而 ,我注意到您只订购直系亲属。你应该订购所有的家属 递归地 在开始锁定之前。而自己的实例也应该 把自己 在要排序的列表中。

    这样,您将安全地锁定每个位置的所有“依赖树”。

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

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