2015-05-05 21 views
12

Gần đây tôi đã hỏi một câu hỏi làm tôi bối rối.Hoán đổi các biến trong môi trường đa luồng

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

Đây không phải là phương pháp an toàn theo chủ đề. Nếu Chủ đề 1 gọi swapEngine(car1, car2) và sau đó Chủ đề 2 gọi swapEngine(car1, car3), có thể cho car2 để kết thúc với công cụ car3. Cách rõ ràng nhất để khắc phục sự cố này là synchronize phương pháp.

Đồng bộ hóa phương pháp đưa ra khả năng không hiệu quả tiềm năng. Nếu Chủ đề 1 gọi số swapEngine(car1, car2) và Chủ đề 2 gọi số swapEngine(car3, car4) thì sao? Không có cách nào hai chủ đề này có thể gây trở ngại cho nhau. Trong trường hợp này, tình huống lý tưởng sẽ là cho hai luồng để hoán đổi động cơ song song. Đồng bộ hóa phương pháp ngăn cản điều này xảy ra.

Có kỹ thuật nào khác để trao đổi các động cơ này theo cách an toàn trong khi vẫn tận dụng lợi thế của tính song song không?

Chỉnh sửa: Phương thức được thực hiện công khai.

+2

Vâng, bạn có thể thêm Khóa (http://docs.oracle.com/javase/7/docs/api/ java/util/concurrent/locks/Lock.html) đối với các phiên bản Car. Sau đó, bạn có thể khóa chỉ những trường hợp tham gia trao đổi. – mkrakhin

+2

@mkrakhin Điều đó có thể là bế tắc dễ bị thay đổi trong một 'hoán đổi (a, b); hoán đổi (b, a); 'tình huống. – Kayaman

+0

@maba, trong trường hợp đó cùng một chiếc xe không thể có trong hai cửa hàng :) – Kishore

Trả lời

5

Khi nhận xét cho biết, bạn có thể tự khóa xe. Điều này, tuy nhiên có thể gây ra một bế tắc nếu xe không phải lúc nào cũng bị khóa theo cùng một thứ tự.

Vì vậy, nếu chiếc xe có một định danh duy nhất, bạn có thể chỉ đơn giản là sắp xếp những chiếc xe, và sau đó trao đổi:

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

Nếu những chiếc xe không có bất kỳ ID duy nhất cho phép để so sánh chúng, bạn có thể sắp xếp chúng bởi hashCode nhận dạng của chúng (thu được bằng cách sử dụng System.identityHashCode(car)). HashCode này, trừ khi bạn có một bộ nhớ khổng lồ, một lượng lớn xe hơi, và may mắn, là duy nhất. Nếu bạn thực sự sợ một tình huống như vậy, thì ổi có một arbitrary ordering mà bạn có thể sử dụng.

0

Nếu bạn lưu trữ Car.engine trong AtomicReference, bạn có thể trao đổi chúng bằng cách sử dụng hoạt động 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); 
      } 
     } 
    }   
} 

Advantage của phương pháp này là nó không đòi hỏi đúng trật tự đối tượng và không sử dụng ổ khóa bên trong. Nó cũng không cần khóa trên đối tượng đầy đủ - các thuộc tính khác có thể được điều khiển song song.

Bất lợi là hiện tại null giá trị là bất hợp pháp: chúng có nghĩa là hoạt động trên biến đang diễn ra. Bạn sẽ cần phải kiểm tra null khi nhận các giá trị và đặt chúng ở bất kỳ đâu nhưng trong hàm tạo:

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; 
    } 
} 
Các vấn đề liên quan