我有一个以下代码片段(代码是用Java编写的,但我试图减少尽可能多的杂乱):
class State {
public synchronized read() {
}
public synchronized write(ResourceManager rm) {
rm.request();
}
public synchronized returnResource() {
}
}
State st1 = new State();
State st2 = new State();
State st3 = new State();
class ResourceManager {
public syncronized request() {
st2 = findIdleState();
return st2.returnResource();
}
}
ResourceManager globalRM = new ResourceManager();
Thread1()
{
st1.write(globalRM);
}
Thread2()
{
st2.write(globalRM);
}
Thread3()
{
st1.read();
}
此代码段可能会通过以下调用序列输入死锁:
Thread1: st1.write()
Thread1: st1.write() invokes globalRM.request()
Thread2: st2.write()
Thread1: globalRM.request() tries to invoke st2.returnResource(), but gets blocked because Thread2 is holding a lock on st2.
Thread2: st2.write() tries to invoke globalRM.request(), but gets blocked because globalRM's lock is with Thread1
Thread3: st2.read(), gets blocked.
如何解决这种僵局?我想了一段时间才看到我可以用来获取锁的某种有序锁方法,但我想不出这样的解决方案。问题在于,资源管理器是全局的,而状态是特定于每个作业的(每个作业都有一个顺序的ID,如果有某种方法可以使用命令进行锁定获取,可以用于排序。)
答案 0 :(得分:1)
有一些选项可以避免这种情况,每种方案都有其优点和缺点:
1。)为所有实例使用单个锁定对象。这种方法很容易实现,但是限制你使用一个线程来获取锁。如果同步块很短并且可伸缩性不是大问题(例如,桌面应用程序也称为非服务器),则这是合理的。其主要卖点是实施简单。
2。)使用有序锁定 - 这意味着无论何时需要获取两个或更多锁,都要确保获取它们的顺序是相同的。这说起来容易得多,可能需要对代码库进行大量更改。
3。)完全摆脱锁。使用java.util.concurrent(.atomic)类,您可以实现多线程数据结构而不会阻塞(通常使用compareAndSet-flavor方法)。这当然需要更改代码库,并需要重新考虑结构。通常需要重写代码库的关键部分。
4。)因此,当您使用不可变类型和对象时,许多问题就会消失。与原子(3.)方法结合使用可实现可变超结构(通常实现为复制更改)。
要提供任何建议,您需要了解更多关于受锁保护的内容的详细信息。
---编辑---
我需要一个无锁的Set实现,这个代码示例说明了它的优点和缺点。我确实将iterator()实现为快照,实现它以抛出ConcurrentModificationException并支持remove()会有点复杂,我不需要它。我没有发布一些引用的实用程序类(我认为它完全明显缺少引用的部分)。
我希望它至少有一点作为起点如何使用AtomicReferences。
/**
* Helper class that implements a set-like data structure
* with atomic add/remove capability.
*
* Iteration occurs always on a current snapshot, thus
* the iterator will not support remove, but also never
* throw ConcurrentModificationException.
*
* Iteration and reading the set is cheap, altering the set
* is expensive.
*/
public final class AtomicArraySet<T> extends AbstractSet<T> {
protected final AtomicReference<Object[]> reference =
new AtomicReference<Object[]>(Primitives.EMPTY_OBJECT_ARRAY);
public AtomicArraySet() {
}
/**
* Checks if the set contains the element.
*/
@Override
public boolean contains(final Object object) {
final Object[] array = reference.get();
for (final Object element : array) {
if (element.equals(object))
return true;
}
return false;
}
/**
* Adds an element to the set. Returns true if the element was added.
*
* If element is NULL or already in the set, no change is made to the
* set and false is returned.
*/
@Override
public boolean add(final T element) {
if (element == null)
return false;
while (true) {
final Object[] expect = reference.get();
final int length = expect.length;
// determine if element is already in set
for (int i=length-1; i>=0; --i) {
if (expect[i].equals(element))
return false;
}
final Object[] update = new Object[length + 1];
System.arraycopy(expect, 0, update, 0, length);
update[length] = element;
if (reference.compareAndSet(expect, update))
return true;
}
}
/**
* Adds all the given elements to the set.
* Semantically this is the same a calling add() repeatedly,
* but the whole operation is made atomic.
*/
@Override
public boolean addAll(final Collection<? extends T> collection) {
if (collection == null || collection.isEmpty())
return false;
while (true) {
boolean modified = false;
final Object[] expect = reference.get();
int length = expect.length;
Object[] temp = new Object[collection.size() + length];
System.arraycopy(expect, 0, temp, 0, length);
ELoop: for (final Object element : collection) {
if (element == null)
continue;
for (int i=0; i<length; ++i) {
if (element.equals(temp[i])) {
modified |= temp[i] != element;
temp[i] = element;
continue ELoop;
}
}
temp[length++] = element;
modified = true;
}
// check if content did not change
if (!modified)
return false;
final Object[] update;
if (temp.length == length) {
update = temp;
} else {
update = new Object[length];
System.arraycopy(temp, 0, update, 0, length);
}
if (reference.compareAndSet(expect, update))
return true;
}
}
/**
* Removes an element from the set. Returns true if the element was removed.
*
* If element is NULL not in the set, no change is made to the set and
* false is returned.
*/
@Override
public boolean remove(final Object element) {
if (element == null)
return false;
while (true) {
final Object[] expect = reference.get();
final int length = expect.length;
int i = length;
while (--i >= 0) {
if (expect[i].equals(element))
break;
}
if (i < 0)
return false;
final Object[] update;
if (length == 1) {
update = Primitives.EMPTY_OBJECT_ARRAY;
} else {
update = new Object[length - 1];
System.arraycopy(expect, 0, update, 0, i);
System.arraycopy(expect, i+1, update, i, length - i - 1);
}
if (reference.compareAndSet(expect, update))
return true;
}
}
/**
* Removes all entries from the set.
*/
@Override
public void clear() {
reference.set(Primitives.EMPTY_OBJECT_ARRAY);
}
/**
* Gets an estimation how many elements are in the set.
* (its an estimation as it only returns the current size
* and that may change at any time).
*/
@Override
public int size() {
return reference.get().length;
}
@Override
public boolean isEmpty() {
return reference.get().length <= 0;
}
@SuppressWarnings("unchecked")
@Override
public Iterator<T> iterator() {
final Object[] array = reference.get();
return (Iterator<T>) ArrayIterator.get(array);
}
@Override
public Object[] toArray() {
final Object[] array = reference.get();
return Primitives.cloneArray(array);
}
@SuppressWarnings("unchecked")
@Override
public <U extends Object> U[] toArray(final U[] array) {
final Object[] content = reference.get();
final int length = content.length;
if (array.length < length) {
// Make a new array of a's runtime type, but my contents:
return (U[]) Arrays.copyOf(content, length, array.getClass());
}
System.arraycopy(content, 0, array, 0, length);
if (array.length > length)
array[length] = null;
return array;
}
}
答案 1 :(得分:0)
任何死锁的答案都是以相同的顺序获取相同的锁。你只需要找到一种方法来做到这一点。