我们的应用程序(使用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)来简化上述逻辑?请帮忙。
答案 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
的调用进行排序也很奇怪,我会在构造函数中执行此操作。