最近我被问到一个难以理解的问题。
public void swapEngine(Car a, Car b) {
Engine temp = a.engine;
a.engine = b.engine;
b.engine = temp;
}
这不是一种线程安全的方法。如果线程1调用swapEngine(car1, car2)
然后线程2调用swapEngine(car1, car3)
,则car2
可能会以car3
的引擎结束。解决此问题最明显的方法是synchronize
方法。
同步该方法会带来潜在的低效率。如果线程1调用swapEngine(car1, car2)
而线程2调用swapEngine(car3, car4)
怎么办?这两个线程决不会相互干扰。在这种情况下,理想的情况是两个线程并行交换引擎。同步该方法可以防止这种情况发生。
还有另一种技术可以在线程安全的方式交换这些引擎,同时仍然利用并行性吗?
编辑:公开方法。
答案 0 :(得分:5)
正如评论所说,你可以自己锁车。但是,如果汽车并非始终以相同的顺序锁定,则可能导致死锁。
因此,如果汽车有唯一标识符,您可以简单地对汽车进行排序,然后交换:
void swapEngine(Car a, Car b) {
Comparator<Car> byId = Comparator.comparing(Car::id);
Car[] cars = new Car[] {a, b};
Arrays.sort(cars, byId);
doSwap(cars[0]), cars[1];
}
private void doSwap(Car a, Car b) {
synchronized(a) {
synchronized(b) {
Engine temp = a.engine;
a.engine = b.engine;
b.engine = temp;
}
}
}
如果汽车没有任何可以比较它们的唯一ID,您可以按照身份hashCode(使用System.identityHashCode(car)
获得)对它们进行排序。除非你拥有巨大的内存,大量的汽车和糟糕的运气,否则这个hashCode是独一无二的。如果你真的害怕这种情况,那么番石榴有arbitrary ordering你可以使用。
答案 1 :(得分:0)
如果您将Car.engine
存储在AtomicReference
中,则可以使用CAS操作交换它们:
public <T> void atomicSwap(AtomicReference<T> a, AtomicReference<T> b) {
for(;;) {
T aa = a.getAndSet(null);
if (aa != null) {
T bb = b.getAndSet(null);
if (bb != null) {
// this piece will be reached ONLY if BOTH `a` and `b`
// contained non-null (and now contain null)
a.set(bb);
b.set(aa);
return;
} else {
// if `b` contained null, try to restore old value of `a`
// to avoid deadlocking
a.compareAndSet(null, aa);
}
}
}
}
这种方法的优点是它不需要正确的对象排序,也不使用内部锁。它也不需要锁定完整对象 - 其他属性可以并行操作。
缺点是现在null
值是非法的:它们意味着对变量的操作正在进行中。获取值并在构造函数中设置它们时,您需要检查null
:
public <T> T getValue(AtomicReference<T> a) {
for(;;) {
T v = a.get();
if (v != null)
return v;
}
}
public <T> T setValue(AtomicReference<T> a, T value) {
for(;;) {
T old = a.get();
if (old != null && a.compareAndSet(old, value))
return old;
}
}