2015-05-05 3 views
12

Меня недавно задал вопрос, который меня озадачил.Переменные переменных в многопоточной среде

public void swapEngine(Car a, Car b) { 
    Engine temp = a.engine; 
    a.engine = b.engine; 
    b.engine = temp; 
} 

Это не потокобезопасный метод. Если Thread 1 вызывает swapEngine(car1, car2), а затем Thread 2 вызывает swapEngine(car1, car3), возможно, что в car2 будет установлен двигатель car3. Наиболее очевидным способом решения этой проблемы является метод synchronize.

Синхронизация метода создает потенциальную неэффективность. Что делать, если Thread 1 вызывает swapEngine(car1, car2) и Thread 2 вызывает swapEngine(car3, car4)? Ни в коем случае эти два потока не могут мешать друг другу. В этом случае идеальной ситуацией будет то, что два потока будут поочередно менять двигатели. Синхронизация метода исключает это из процесса.

Есть ли другой способ обмена этими двигателями в потокобезопасной манере, все еще используя параллелизм?

Редактировать: Использовать способ публичный.

+2

Ну, вы могли бы добавить блокировку (http://docs.oracle.com/javase/7/docs/api/ java/util/concurrent/locks/Lock.html) к экземплярам автомобилей. Затем вы можете заблокировать только те экземпляры, участвующие в обмене. – mkrakhin

+2

@mkrakhin Это будет тупик, подверженный «свопу» (a, b); swap (b, a); ситуация. – Kayaman

+0

@maba, в этом случае тот же автомобиль не может быть в двух магазинах :) – Kishore

ответ

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

Если автомобили не имеют уникальный идентификатор, позволяющий сравнить их, вы можете сортировать их по их идентификатору hashCode (полученному с использованием System.identityHashCode(car)). Этот хэш-код, если у вас нет огромной памяти, огромного количества автомобилей и неудачи, уникален. Если вы действительно боитесь такой ситуации, тогда у Guava есть arbitrary ordering, который вы можете использовать.

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; 
    } 
} 
Смежные вопросы