2012-03-06 35 views
6

Tôi đang cố gắng thiết kế một lớp như một Mã Kata cho bản thân mình có một thuộc tính giá trị có thể được đặt và lớp có thể phát hành các cá thể ValueListener. Ý tưởng là có một ví dụ của ValueHolder với nhiều chủ đề khách hàng truy cập đồng thời. Mỗi chuỗi khách hàng đã yêu cầu một ValueWatcher và đã gọi waitForValue().Thông báo cho chủ đề khi thay đổi bộ đếm

Điều tôi thực sự đấu tranh là điều kiện nào tôi nên sử dụng trên vòng lặp while xung quanh wait() để tránh các thông báo giả mạo (tức là giá trị không thay đổi). Tôi có thể thấy rằng thiết kế này có thể làm cho nó có thể của trường hợp ValueWatcher để bỏ lỡ bản cập nhật, nhưng ít lo lắng về điều đó ở giai đoạn này.

Sẽ đánh giá cao bất kỳ hướng dẫn nào về ưu đãi!

public class ValueHolder { 

    private int value = 0; 
    private final Object monitor = new Object(); 

    public void setValue(int value) { 
    synchronized(monitor) { 
     this.value = value; 
     monitor.notifyAll(); 
    } 
    } 

    ValueWatcher createChangeWatcher() { 
    return new ValueWatcher(); 
    } 

    private class ValueWatcher { 
    public int waitForValue() { 
     synchronized(monitor) { 
     while (==== ??? =====) { 
      monitor.wait(); 
      return value; 
     } 
     } 
    } 
    }  
} 
+0

Bạn có thấy thông báo giả mạo không? Nếu chỉ updater gọi 'notification' hoặc' notifyall' thì tôi không thấy bạn sẽ như thế nào. – Gray

+0

Có thể lệnh gọi 'setValue' chỉ nên thông báo cho người khác nếu' giá trị' thực sự đã thay đổi? Ngay bây giờ, nó có thể được cập nhật với cùng giá trị và mọi người được thông báo. Đó là điều bạn muốn? – Gray

+0

Tôi không thấy lý do tại sao có một nhu cầu cho một vòng lặp 'while' ở tất cả. – biziclop

Trả lời

3

vấn đề thú vị. Đây là một giải pháp trên đỉnh đầu của tôi. Có số phiên bản cùng với giá trị đang được thay đổi. Bất cứ khi nào giá trị được cập nhật, số phiên bản cũng được tăng lên để các đối tượng ValueWatcher có thể kiểm tra xem phiên bản có tăng lên hay không có nghĩa là một sự thay đổi đã xảy ra.

Chỉnh sửa: Ban đầu tôi có một AtomicLong nhưng tôi đang ăn cắp ý tưởng về một đối tượng bao bọc từ @John Vint.

private final VersionValue versionValue = new VersionValue(); 

public void setValue(int value) { 
    synchronized (monitor) { 
     versionValue.value = value; 
     versionValue.version++; 
     monitor.notifyAll(); 
    } 
} 

private class ValueWatcher { 
    private long localVersion = 0; 
    public int waitForValue() { 
     synchronized (monitor) { 
      while (true) { 
       if (localVersion < versionValue.version) { 
        // NOTE: the value might have been set twice here 
        localVersion = versionValue.version; 
        return versionValue.value; 
       } 
       monitor.wait(); 
      } 
     } 
    } 
} 

private static class VersionValue { 
    int value; 
    long version; 
} 

Ngoài ra, mặc dù wakeups giả mạo có thể xảy ra, điều quan trọng là hãy nhớ rằng các văn bản:

Luôn gọi chờ đợi bên trong một vòng lặp rằng các xét nghiệm cho điều kiện được chờ đợi. Đừng cho rằng sự gián đoạn là vì điều kiện cụ thể mà bạn đang chờ đợi, hoặc điều kiện đó vẫn đúng.

Thông tin chi tiết về điều kiện chủng tộc và mô hình nhà sản xuất/người tiêu dùng hơn là đánh thức giả mạo. Xem my page here about that.

+0

Để làm điều đó mà không có chủng tộc, bạn sẽ phải kết hợp số phiên bản với giá trị trong một loại nguyên tử duy nhất (ví dụ: 'AtomicLong versionedValue = value | (dài) phiên bản << 0xffff'. – ninjalj

+0

Ồ tôi hiểu rồi. Tôi cần phải di chuyển Đồng bộ hóa của tôi bên ngoài trong thời gian ngay.Thêm @ninjalj – Gray

+0

race: bạn nhận localCounter để cập nhật 'n' và trả về' value' của cập nhật 'n + 1'. Tiếp theo thời gian bạn nhận được' localCounter' để cập nhật 'n + 1' và trả về giá trị 'không thay đổi' – ninjalj

0
private class ValueWatcher { 
    private int oldValue = 0; 

    public int waitForValue() { 
    synchronized(monitor) { 
     while (value == oldValue) { 
     monitor.wait(); 
     } 
     oldValue = value 
     return oldValue; 
    } 
    } 
} 
+0

Có một cuộc đua ở đây trong trường hợp giá trị thay đổi và sau đó được thay đổi trở lại. – Gray

+0

Điều đó có một vấn đề rõ ràng: Nếu chúng ta đặt giá trị cho giá trị hiện tại (nghĩa là 'newVal == oldVal'), chúng ta sẽ mất một bản cập nhật. [Vấn đề ABA] (http://en.wikipedia.org/wiki/ABA_problem) – Voo

+0

Ngoài ra tôi nghĩ có một lỗi ở đó sẽ gây ra một vòng lặp vô hạn. Tôi nghĩ bạn muốn nói 'if (oldValue! = Value) {oldValue = value; break} ' – Gray

2

Tất cả những gì bạn thực sự quan tâm là nếu giá trị thay đổi sau khi bạn nhập phương thức và khối được đồng bộ hóa của nó. Vì vậy, hãy lấy dấu thời gian của lần cuối cùng một giá trị đã được thay đổi và chỉ tiếp tục khi dấu thời gian cập nhật cuối cùng> khi bạn đã nhập.

private final StampedValue stamped = new StampedValue(); 

    public void setValue(int value) { 
     synchronized (monitor) { 
      this.stamped.value = value; 
      this.stamped.lastUpdated = System.currentTimeMillis(); 
      monitor.notifyAll(); 
     } 
    } 
    private class ValueWatcher { 

     public int waitForValue() { 
      synchronized(monitor) { 
       long enteredOn = System.currentTimeMillis();  
       while (enteredOn > stamped.lastUpdated) { 
        monitor.wait(); 
       } 
       return stamped.value; 
      } 
     } 
    } 
    private class StampedValue { 
     long lastUpdated = System.currentTimeMillis(); 
     int value; 
    } 
+0

Có một số vấn đề trong vòng lặp' while' @ John. Tôi nghĩ rằng bạn muốn trở lại bên ngoài của nó có thể? – Gray

+0

Ngoài ra, lần đầu tiên thông qua nó có khả năng là nếu giá trị chưa được đặt, 'waitForValue()' sẽ trả về 0 ngay lập tức bởi vì «enterOn' là bây giờ và' lastUpdated' là 0. – Gray

+0

@Gray Yea sao chép và dán không thành công tôi –

0
public class ValueHolder { 

    private final Object monitor = new Object(); 
    private LinkedList<WeakReference<ValueWatcher>> waiters = new LinkedList<WeakReference<ValueWatcher>>(); 

    public void setValue(int value) { 
     synchronized (monitor) { 
      Iterator<WeakReference<ValueWatcher>> it = waiters.iterator(); 
      while (it.hasNext()) { 
       WeakReference<ValueWatcher> ref = it.next(); 
       if (ref.get() == null) 
        it.remove(); 
       else 
        ref.get().waitingList.add(value); 
      } 
      monitor.notifyAll(); 
     } 
    } 

    ValueWatcher createChangeWatcher() { 
     ValueWatcher ret = new ValueWatcher(); 
     synchronized(monitor) { 
      waiters.add(new WeakReference<ValueWatcher>(ret)); 
     } 
     return ret; 
    } 

    private class ValueWatcher { 

     private Queue<Integer> waitingList = new LinkedList<Integer>(); 

     public int waitForValue() { 
      synchronized (monitor) { 
       while (waitingList.isEmpty()) { 
        monitor.wait(); 
       } 
       return waitingList.poll(); 
      } 
     } 
    } 
} 

Ý tưởng là bạn theo dõi những người đang chờ đợi và mỗi quan sát có một danh sách các giá trị đã được thiết lập kể từ khi gọi cuối cùng của waitForValue(). Điều này loại bỏ sự cần thiết phải lưu trữ value trong ValueHolder, đó là một điều tốt là do thời gian ValueWatcher tỉnh dậy, điều đó có thể đã thay đổi nhiều lần. Hạn chế của phương pháp này là bạn có thể thấy là việc tạo người theo dõi mới sẽ chặn cho đến khi màn hình được miễn phí.

1

Điều gì về mỗi người nghe có một số BlockingQueue mà nó mang lại cho chuỗi giá trị thiết lập như là một phần của đăng ký với tư cách người nghe? Sau đó, khi giá trị được thay đổi, chuỗi giá trị thiết lập chỉ đơn giản là vòng lặp trên mỗi hàng đợi đó, cho nó giá trị mới. Bạn có thể muốn sử dụng BlockingQueue.offer trong vòng lặp đó, để nếu một luồng chưa sẵn sàng để nhận giá trị mới, nó sẽ không ngăn các luồng khác nhận được nó.

Đây có thể không phải là cách tiếp cận hiệu quả nhất, nhưng nó đơn giản và cấu trúc đồng thời (ví dụ phần cứng) được kiểm tra và duy trì tốt cho bạn. Và nó không phải là không hiệu quả.

+0

+1 Đây sẽ là phiên bản đơn giản của những gì tôi đã làm. – biziclop

Các vấn đề liên quan