2009-10-27 31 views
10

Tôi đang cố tạo một bản thực thi hàng đợi không có khóa trong Java, chủ yếu cho việc học cá nhân. Hàng đợi phải là hàng đợi chung, cho phép bất kỳ số lượng người đọc và/hoặc người viết nào đồng thời.Đây có phải là (Khóa-Miễn phí) Thực thi Hàng đợi An toàn Chủ đề không?

Bạn có vui lòng xem lại và đề xuất bất kỳ cải thiện/vấn đề nào bạn tìm thấy không?

Cảm ơn bạn.

import java.util.concurrent.atomic.AtomicReference; 

public class LockFreeQueue<T> { 
    private static class Node<E> { 
     E value; 
     volatile Node<E> next; 

     Node(E value) { 
      this.value = value; 
     } 
    } 

    private AtomicReference<Node<T>> head, tail; 

    public LockFreeQueue() { 
     // have both head and tail point to a dummy node 
     Node<T> dummyNode = new Node<T>(null); 
     head = new AtomicReference<Node<T>>(dummyNode); 
     tail = new AtomicReference<Node<T>>(dummyNode); 
    } 

    /** 
    * Puts an object at the end of the queue. 
    */ 
    public void putObject(T value) { 
     Node<T> newNode = new Node<T>(value); 
     Node<T> prevTailNode = tail.getAndSet(newNode); 
     prevTailNode.next = newNode; 
    } 

    /** 
    * Gets an object from the beginning of the queue. The object is removed 
    * from the queue. If there are no objects in the queue, returns null. 
    */ 
    public T getObject() { 
     Node<T> headNode, valueNode; 

     // move head node to the next node using atomic semantics 
     // as long as next node is not null 
     do { 
      headNode = head.get(); 
      valueNode = headNode.next; 
      // try until the whole loop executes pseudo-atomically 
      // (i.e. unaffected by modifications done by other threads) 
     } while (valueNode != null && !head.compareAndSet(headNode, valueNode)); 

     T value = (valueNode != null ? valueNode.value : null); 

     // release the value pointed to by head, keeping the head node dummy 
     if (valueNode != null) 
      valueNode.value = null; 

     return value; 
} 
+0

Ported để Mã xét tại http://codereview.stackexchange.com/câu hỏi/224/là-này-lock-free-queue-thực hiện-thread-an toàn –

Trả lời

4

Mã không an toàn chỉ. Cân nhắc putObject(...):

public void putObject(T value) { 
    Node<T> newNode = new Node<T>(value); 
    Node<T> prevTailNode = tail.getAndSet(newNode); 
    prevTailNode.next = newNode; 
} 

Tuyên bố thứ 2 bổ sung thêm các nút mới trước khi con trỏ next nút trước đó đã được thiết lập. Điều đó chỉ xảy ra trong câu lệnh thứ 3. Do đó, có một cửa sổ trong đó nextnull; tức là một điều kiện chủng tộc.

Ngay cả khi bạn đã khắc phục điều đó, vẫn còn một vấn đề ngớ ngẩn hơn. Chủ đề đọc trường next cho đối tượng Nút sẽ không nhất thiết phải xem giá trị mà chuỗi thứ hai vừa mới viết. Đó là kết quả của mô hình bộ nhớ Java. Trong trường hợp này, cách để đảm bảo rằng sau đọc luôn thấy giá trị được viết trước đó là một trong hai:

  • tuyên bố nextvolatile, hoặc
  • làm cả hai việc đọc và viết trong một mutex nguyên thủy trên cùng một đối tượng.

EDIT: trên đọc mã cho getObject()putObject() cụ thể hơn, tôi có thể thấy rằng không có gì buộc giá trị không null của next được đỏ mặt vào bộ nhớ trong putObject, và không có gì lực lượng getObject để đọc next từ chính ký ức. Vì vậy, mã getObject có thể thấy giá trị sai của next, khiến mã trả về null khi có thực sự là một phần tử trong hàng đợi.

+0

Xin lỗi, nhưng bạn là sai với tiếp theo là null. Trên thực tế, tail.next luôn luôn là null, và nó không gây ra bất kỳ vấn đề gì. Nếu tiếp theo là null trong getObject, vòng lặp kết thúc và null được trả về (hàng đợi rỗng). Ngoài ra, vòng lặp trong getObject có thể quay chỉ khi một luồng khác đã viết thành công - ví dụ: các chủ đề khác đã thành công, đó là những gì chúng ta muốn từ các thuật toán không khóa. – jpalecek

+0

@jpalecek - cố định. –

+0

Xin lỗi, nhưng nó vẫn không chính xác, trong nhiều lĩnh vực (toàn bộ EDIT2, ví dụ, chỉ ra bạn đã không nhận thấy hàng đợi luôn luôn chứa ít nhất một, nút giả). – jpalecek

1

tôi thấy chỉ có hai vấn đề với mã của bạn:

  • là một trong những vấn đề với bộ nhớ hoạt động ra lệnh cho Stephen C đề cập (có thể được giải quyết bằng cách tuyên bố nextvaluevolatile) (Node: giá trị có cùng một vấn đề)

  • giây thứ hai là tinh tế hơn và không đồng thời có liên quan: sau khi bạn trả về một đối tượng trong getObject, bạn vẫn giữ nguyên tham chiếu của nó từ đầu. Điều này có thể dẫn đến rò rỉ bộ nhớ.

Nếu không thì thuật toán là OK. Một cuộc biểu tình mơ hồ (giả định ở trên là cố định):

L1: tail không bao giờ có thể bị xóa khỏi hàng đợi. Điều này giữ bởi vì khi một cái gì đó được lưu trữ trong tail, nó có next == null.Ngoài ra, khi bạn chỉ định một cái gì đó cho xxx.next (chỉ trong putObject), nó không thể là tail, vì nguyên tử của getAndSet và thứ tự giữa ghi dễ bay hơi và đọc tiếp - giả sử bạn đọc một không rỗng tail.next, giá trị này phải được viết bởi putObject và do đó xảy ra sau dòng cuối cùng của nó. Điều này có nghĩa là số xảy ra sau dòng trước đó, có nghĩa là giá trị mà chúng tôi đang đọc không phải là từ tail.

Hậu quả của việc này là mọi đối tượng trong putObject cuối cùng sẽ có thể truy cập từ head. Đó là vì chúng tôi đang kết nối sau tail và nút này chỉ có thể bị xóa sau khi chúng tôi ghi tham chiếu của nút mới đến số next, điều này có nghĩa là nút mới có thể truy cập từ head.

Các đối tượng được thêm vào được sắp xếp hợp lý theo hoạt động getAndSet trong putObject.

Các đối tượng bị bốc hơi được sắp xếp theo các hoạt động thành công compareAndSet trong getObject.

getObject/putObject được sắp xếp theo viết/đọc cho trường dễ bay hơi next.

+0

Làm thế nào để bạn có nghĩa là "bạn vẫn giữ lại tham chiếu của nó từ đầu"? 'head' được đặt thành' nextNode' trong phương thức đó. – Joren

+1

Ý tôi là, 'giá trị' nằm trong' nextNode' được trả về và được giữ lại thông qua 'đầu'. Nếu không có thêm phần tử nào được lấy từ hàng đợi (chẳng hạn như nếu hàng đợi trống sau đó), tham chiếu đến giá trị trả về sẽ không bao giờ được giải phóng. – jpalecek

+0

Cảm ơn bạn! Tôi đã khắc phục vấn đề rò rỉ bộ nhớ, và làm cho 'tiếp theo 'dễ bay hơi. Nhưng tôi chưa hiểu tại sao 'giá trị' cần phải biến động. Bạn có thể giải thích thêm? –

1

Tôi tin rằng bạn sẽ nhận được một NPE khi bạn cố gắng "giải phóng giá trị ..." nếu bạn gọi

new LockFreeQueue<?>().getObject(); 

vì bạn không thực hiện bất kỳ kiểm tra vô hiệu trên valueNode ở đó, mặc dù bảo vệ chống lại nó ở trên.

+0

Bạn nói đúng. Tôi đã bỏ lỡ điều đó hoàn toàn! Tôi đã sửa nó ngay bây giờ. Cảm ơn bạn! –

1

Bạn nên xem xét việc thực hiện các java.util.concurrent.ConcurrentLinkedQueue http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/ConcurrentLinkedQueue.html Nó khá nhiều những gì bạn đang cố gắng để đạt được

+0

Cảm ơn bạn. Tôi sẽ kiểm tra nó ra để có thêm ý tưởng. Tuy nhiên, 'ConcurrentLinkedQueue' quá phức tạp, vì nó hỗ trợ nhiều phương thức, trong khi hàng đợi của tôi đơn giản hơn nhiều, cho phép tôi đưa ra nhiều giả định hơn và thử tối ưu hơn. –

+1

Có một lý lẽ cho rằng mã khóa tự do phức tạp. ;) –

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