2010-04-12 1 views
9

Tôi đang đọc "Java Concurrency trong thực tế" và xem mã ví dụ ở trang 51.Cách chứng minh điều kiện chủng tộc xung quanh các giá trị không được xuất bản đúng cách?

Theo sách, đoạn mã này có nguy cơ thất bại nếu nó chưa được xuất bản đúng cách. Bởi vì tôi thích viết mã ví dụ và phá vỡ chúng để chứng minh cách chúng hoạt động. Tôi đã cố gắng để làm cho nó ném một AssertionError nhưng đã thất bại. (Dẫn tôi đến số previous question)

Có thể ai đó đăng mã mẫu để một AssertionError được ném không? Quy tắc: Không sửa đổi lớp Chủ.

public class Holder{ 
    private int n; 

    public Holder(int n){ 
     this.n = n; 
    } 

    public void assertSanity(){ 
     if (n != n) { 
      throw new AssertionError("This statement is false"); 
     } 
    } 
} 

Tôi đã sửa đổi lớp để làm cho nó dễ vỡ hơn nhưng tôi vẫn không thể nhận được một AssertionError ném.

class Holder2 { 
    private int n; 
    private int n2; 

    public Holder2(int n) throws InterruptedException{ 
     this.n = n; 
     Thread.sleep(200); 
     this.n2 = n; 
    } 

    public void assertSanity(){ 
     if (n != n2) { 
      throw new AssertionError("This statement is false"); 
     } 
    } 
} 

Có thể thực hiện một trong các lớp ở trên ném một AssertionError không? Hay chúng ta phải chấp nhận rằng đôi khi họ có thể làm như vậy và chúng ta không thể viết mã để chứng minh điều đó?

+0

Tại sao có cả hai câu hỏi? Chúng giống nhau. http://stackoverflow.com/questions/2614066/java-concurrency-in-practice-sample-question – Instantsoup

+0

tôi tin rằng câu trả lời là giống như gustafc đưa ra trong q khác của bạn - rằng bạn không xuất bản một tham chiếu đến đối tượng cho đến khi hàm tạo hoàn tất. – oedo

+0

Trên một lưu ý phụ: 'AssertionError' được dự định sử dụng nội bộ bởi cấu trúc ngôn ngữ' assert' của Java .... Tôi sẽ không sử dụng nó trực tiếp. khẳng định cũng an toàn để sử dụng trong mã sản xuất, bởi vì các xác nhận bị vô hiệu hóa theo mặc định (bạn có thể bật chúng bằng công tắc dòng lệnh '-esa'). – Powerlord

Trả lời

3

Tôi muốn chạy trên một máy đa trong một vài giờ và xem những gì sẽ xảy ra (loại bỏ các giấc ngủ nếu bạn sử dụng Holder2 của bạn) . Các điều kiện chủng tộc như vậy có thể hiếm, hoặc không tồn tại trên máy tính cụ thể của bạn - nhưng ít nhất cố gắng kích động chúng trên một triệu trường hợp, bằng cách thử hàng triệu lần.

class Checker { 
    private Holder h; 
    public Checker() { 
    h = new Holder(42); 
    } 

    public void check() { 
    h.assertSanity(); 
    } 

    public void create(int n) { 
    h = new Holder(n); 
    } 

} 

public class MyThread extends thread{ 
    private bool check; 
    private final Checker c; 
    public MyThread(bool check,Checker c) { 
    this.check = check; 
    this.c = c; 
    } 
    public static void main(String[] args) { 
     Checker c = new Checker(); 
     MyThread t1 = new MyThread(false,c); 
     MyThread t2 = new MyThread(true,c); 
     t1.start(); 
     t2.start(); 
     t1.join(); 
     t2.join(); 
    } 
    public void run() { 
    int n = 0; 
    while(true) { 
     if(check) 
     c.check(); 
     else 
     c.create(n++); 
    } 
    } 
} 
} 
-1

Bạn không thể thay đổi giá trị của n bất cứ lúc nào bằng cách sử dụng:

Holder h = new Holder(5); 
    Field f = h.getClass().getDeclaredField("n"); 
    f.setAccessible(true); 
    f.setInt(h, 10); 
    h.assertSanity(); 
1

Như BobbyShaftoe đã nói trong chủ đề khác, bạn không thể dựa vào việc chỉ chạy mã có thể hoặc không thể xảy ra. Nếu bạn nghĩ về điều này từ một cấp độ hội, nó sẽ rất khó khăn cho n! = N vì nó là rất ít cuộc gọi và dựa vào quá trình được chuyển ra tại một thời gian thực sự chính xác.

Nếu bạn muốn có thể cho biết liệu hệ thống đồng thời có hợp lệ hay không thì tốt hơn là nên mô hình hóa hệ thống bằng cách sử dụng một cái gì đó như Hệ thống chuyển đổi được gắn nhãn. Hãy thử công cụ LTSA nếu bạn quan tâm đến việc chứng minh đồng thời hoặc tìm lỗi.

http://www.doc.ic.ac.uk/ltsa/

+1

Tôi không đồng ý với câu đầu tiên của bạn. Chạy mã * có thể * chứng minh rằng có thể xảy ra lỗi * nếu xảy ra lỗi. Nhưng bạn đúng rằng không có số lượng thử nghiệm có thể chứng minh sự vắng mặt của các lỗi. –

+0

Mike bạn hoàn toàn đúng. Tôi có một chút mang đi với từ ngữ của tôi. – Oli

1

Trong ví dụ cuốn sách mà là cho các lớp Chủ không phải là trực tiếp nguyên nhân của vấn đề, trên thực tế nó nói rằng:

Vấn đề ở đây không phải là lớp Chủ bản thân, nhưng Chủ sở hữu không được xuất bản đúng cách. Tuy nhiên, Chủ có thể được miễn nhiễm với việc xuất bản không đúng bằng cách tuyên bố trường n là cuối cùng, điều này sẽ làm cho Chủ bất biến; xem Phần 3.5.2.

Chỉ cần trước khi này nó đề cập đến đoạn mã sau, mà nó là đối tượng của vấn đề:

// Unsafe publication 
public Holder holder; 
public void initialize() { 
    holder = new Holder(42); 
} 

Vì vậy, để tái tạo nó, bạn sẽ cần phải tạo ra một lớp nhà xuất bản và hai luồng, một trong đó cuộc gọi khởi tạo và một cuộc gọi xác nhận.

Có nói rằng, tôi đã cố gắng để tái tạo nó bản thân mình và vẫn không làm như vậy :(

Dưới đây là nỗ lực đầu tiên của tôi, tuy nhiên có một lời giải thích rõ hơn về vấn đề ở http://forums.oracle.com/forums/thread.jspa?threadID=1140814&tstart=195

public class HolderTest { 

    @Test 
    public void testHolder() throws Exception { 
    for (int i = 0; i < 1000000000; i++) { 
     final CountDownLatch finished = new CountDownLatch(2); 

     final HolderPublisher publisher = new HolderPublisher(); 

     final Thread publisherThread = new Thread(new Publisher(publisher, 
      finished)); 
     final Thread checkerThread = new Thread(new Checker(publisher, 
      finished)); 

     publisher.holder = null; 

     publisherThread.start(); 
     checkerThread.start(); 

     finished.await(); 
    } 
    } 

    static class Publisher implements Runnable { 

    private final CountDownLatch finished; 
    private final HolderPublisher publisher; 

    public Publisher(final HolderPublisher publisher, 
     final CountDownLatch finished) { 
     this.publisher = publisher; 
     this.finished = finished; 
    } 

    @Override 
    public void run() { 
     try { 
     publisher.initialize(); 
     } finally { 
     finished.countDown(); 
     } 
    } 

    } 

    static class Checker implements Runnable { 

    private final CountDownLatch finished; 
    private final HolderPublisher publisher; 

    public Checker(final HolderPublisher publisher, 
     final CountDownLatch finished) { 
     this.publisher = publisher; 
     this.finished = finished; 
    } 

    @Override 
    public void run() { 
     try { 
     publisher.holder.assertSanity(); 
     } catch (final NullPointerException e) { 
     // This isnt the error we are interested in so swallow it 
     } finally { 
     finished.countDown(); 
     } 
    } 

    } 

    static class HolderPublisher { 

    // Unsafe publication 
    public Holder holder; 

    public void initialize() { 
     holder = new Holder(42); 
    } 

    } 
} 
0

tôi không nghĩ rằng các lỗi khẳng định có thể xảy ra mà không sửa đổi các lớp Holder. tôi nghĩ rằng cuốn sách là sai.

lý do duy nhất để gây ra lỗi khẳng định là khi assertSanity() được gọi trên một đối tượng được xây dựng một phần. Làm thế nào có thể một sợi, khác với thread constructor, tham chiếu một đối tượng được xây dựng một phần? AFAIK, chỉ có thể có trong hai trường hợp sau:

  1. Xuất bản this trong hàm tạo. Ví dụ. chỉ định this cho biến được chia sẻ. Điều này không thể xảy ra trong mã mẫu của chúng tôi bởi vì hàm tạo của Holder không làm điều đó.
  2. Lớp bên trong không tĩnh của lớp học có thể tham chiếu đến lớp cha của nó ngay cả khi phụ huynh của nó được xây dựng một phần. Điều này không thể xảy ra vì Chủ sở hữu không có bất kỳ lớp bên trong nào.

Lưu ý rằng đoạn mã sau trong cuốn sách không công bố bất kỳ đối tượng xây dựng phần:

public class GoodCode { 
    public Holder holder; 
    public void initialize() { 
    holder = new Holder(42); 
    } 
} 

Nếu bạn tháo rời initialize(), bạn nhận được như sau:

public void initialize(); 
    Code: 
    0: aload_0  
    1: new   #2     // class Holder 
    4: dup   
    5: bipush  42 
    7: invokespecial #3     // Method Holder."<init>":(I)V 
    10: putfield  #4     // Field holder:LHolder; 
    13: return   

Lưu ý rằng putfield holder thực hiện sau invokespecial <init>. Điều này có nghĩa việc gán holder xảy ra sau khi hàm tạo được hoàn thành. Đối tượng được xây dựng một phần chỉ được lưu trữ trong ngăn xếp của luồng. Nó không được xuất bản.

Nếu bạn có thể kích hoạt lỗi xác nhận một cách hợp lý (ví dụ: phản chiếu không hợp lý), hãy đặt nó ở đây. Tôi sẽ bỏ phiếu cho bạn.