在多线程环境中交换变量

时间:2015-05-05 14:28:30

标签: java

最近我被问到一个难以理解的问题。

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)怎么办?这两个线程决不会相互干扰。在这种情况下,理想的情况是两个线程并行交换引擎。同步该方法可以防止这种情况发生。

还有另一种技术可以在线程安全的方式交换这些引擎,同时仍然利用并行性吗?

编辑:公开方法。

2 个答案:

答案 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; 
    }
}