2011-02-07 43 views
14

Điều này nói rằng việc gán trường luôn luôn là nguyên tử ngoại trừ các trường dài hoặc gấp đôi.Tại sao khóa kiểm tra kép bị hỏng trong Java?

Tuy nhiên, khi tôi đọc một Giải thích về việc tại sao hai séc khóa bị hỏng, nó nói rằng vấn đề là trong hoạt động chuyển nhượng:

// Broken multithreaded version 
// "Double-Checked Locking" idiom 
class Foo { 
    private Helper helper = null; 
    public Helper getHelper() { 
     if (helper == null) { 
      synchronized(this) { 
       if (helper == null) { 
        helper = new Helper(); 
       } 
      } 
     } 
     return helper; 
    } 

    // other functions and members... 
} 
  1. thông báo Chủ đề Một rằng giá trị không được khởi tạo , do đó, nó có được khóa và bắt đầu khởi tạo giá trị .
  2. Do ngữ nghĩa của một số ngôn ngữ lập trình, mã tạo ra bởi trình biên dịch được phép để cập nhật các biến dùng chung để chỉ đến một đối tượng xây dựng một phần trước khi A đã hoàn thành thực hiện khởi tạo.
  3. Chủ đề B thông báo rằng biến chia sẻ đã được khởi tạo (hoặc là nó xuất hiện) và trả về giá trị của nó. Bởi vì chủ đề B tin rằng giá trị là đã được khởi tạo, nó không có được khóa. Nếu B sử dụng đối tượng trước khi tất cả quá trình khởi tạo được thực hiện bởi B (hoặc bởi vì A chưa hoàn thành khởi tạo hoặc vì một số giá trị được khởi tạo trong đối tượng chưa được vi phạm vào bộ nhớ B sử dụng (bộ nhớ cache kết hợp)), chương trình có thể sẽ bị lỗi .
    (từ http://en.wikipedia.org/wiki/Double-checked_locking).

Khi nào có thể? Có thể là trên hoạt động gán JVM 64 bit không phải là nguyên tử? Nếu không thì liệu "khóa kiểm tra kép" có bị hỏng không?

+1

Không có sự khác biệt trên JVM 64 bit. – rapadura

+0

có thể trùng lặp của http://stackoverflow.com/questions/12448864/java-double-locking-can-someone-explain-more-simply-why-intuition-wouldnt-wor/12449110?noredirect=1#comment26446551_12449110 –

Trả lời

15

Vấn đề không phải là nguyên tử, nó đặt hàng. JVM được phép sắp xếp lại các hướng dẫn để cải thiện hiệu suất, miễn là happens-before không bị vi phạm. Do đó, thời gian chạy theo lý thuyết có thể lập lịch lệnh mà cập nhật helper trước khi tất cả các hướng dẫn từ hàm tạo của lớp Helper đã được thực hiện.

+0

nó luôn luôn đặt hàng, không phải là nó :). và đó là câu trả lời đúng – bestsss

2

Một số bài tập có thể cần thiết để xây dựng thể hiện của Helper bên trong hàm tạo và ngữ nghĩa cho phép chúng được sắp xếp lại theo phân công helper = new Helper().

Vì vậy, trường helper có thể được gán một tham chiếu đến một đối tượng không phải tất cả các bài tập đã diễn ra, do đó nó không được khởi tạo đầy đủ.

+0

việc cần làm bạn có nghĩa là "một số bài tập"? Tôi chỉ thấy 1: 'helper = new Helper()'. Nếu nó có thể được xây dựng "tại chỗ" thì bạn có thể đưa ra một liên kết chứng minh rằng java thực sự làm những điều kỳ lạ? Cám ơn. – Roman

+1

@Roman: Như đã đề cập trong các dấu ngoặc kép trong câu hỏi của bạn, mã được trình biên dịch tạo ra được phép gán 'Trình trợ giúp' cho một cá thể của' Trình trợ giúp' mà _không hoàn tất xây dựng_. – ColinD

2

đúp kiểm tra khóa trong java có một loạt các vấn đề:

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

+5

Đối với Java 5+ nó hoạt động như mong đợi mà không có vấn đề gì nếu bạn khai báo trường đang được khởi tạo là 'biến động' (như đã đề cập trong bài báo đó, tất nhiên). – ColinD

+0

thanx cho liên kết, rất thú vị. – Roman

7

Việc chuyển nhượng tài liệu tham khảo là nguyên tử, nhưng việc xây dựng không phải là! Vì vậy, như đã nêu trong phần giải thích, giả sử thread B muốn sử dụng singleton trước khi Thread A đã xây dựng xong nó, nó không thể tạo một cá thể mới vì tham chiếu không phải là rỗng, vì vậy nó chỉ trả về đối tượng được xây dựng một phần.

Nếu bạn không chắc chắn rằng xuất bản tài liệu tham khảo chia sẻ xảy ra trước khi khác tải chủ đề đó chia sẻ tham khảo, thì ghi của tham chiếu đến đối tượng mới có thể được sắp xếp lại với viết để lĩnh vực của mình .Trong trường hợp đó, một chuỗi khác có thể thấy giá trị cập nhật cho tham chiếu đối tượng nhưng đã lỗi thời giá trị cho một số hoặc tất cả trạng thái của đối tượng - một đối tượng được xây dựng một phần . - Brian Goetz: Java Đồng thời trong Thực hành

Vì kiểm tra ban đầu cho null không được đồng bộ hóa nên không có ấn phẩm nào và việc sắp xếp lại này là có thể.

+0

là nó tuyên bố bất cứ nơi nào mà java làm nhiệm vụ đầu tiên và chỉ sau đó tính toán phần bên phải? (Đó là loại hình 'Tương lai', nhưng tôi chưa bao giờ thấy những phát biểu chính thức về cách sử dụng nó bởi trình biên dịch java). – Roman

+0

@Roman: Nó không phải là một vấn đề đơn giản của "Java gán đầu tiên và chỉ sau đó tính toán phần bên phải". Đó sẽ là một tuyên bố sai lầm mà tôi nghĩ. Java cho phép mã mức thấp sắp xếp lại các câu lệnh theo cách có thể gây ra các kết quả không mong muốn khi một trường được chia sẻ không phải là 'biến động' hoặc' cuối cùng' được truy cập bởi một luồng khác. – ColinD

+0

@Roman xem câu trả lời được cập nhật ... – Robert

0

Tôi xin lỗi, điều này có thể hơi không liên quan đến câu hỏi, tôi chỉ tò mò thôi. Trong trường hợp này sẽ không tốt hơn nếu bạn lấy khóa trước khi chuyển nhượng và/hoặc trả lại giá trị? Giống như:

private Lock mLock = new ReentrantLock(); 
private Helper mHelper = null; 

private Helper getHelper() { 
    mLock.lock(); 
    try { 
     if (mHelper == null) { 
      mHelper = new Helper(); 
     } 
     return mHelper; 
    } 
    finally { 
     mLock.unlock(); 
    } 
} 

Hoặc có lợi thế nào khi sử dụng khóa đã kiểm tra kép không?

-1
/*Then the following should work. 
    Remember: getHelper() is usually called many times, it is BAD 
    to call synchronized() every time for such a trivial thing! 
*/ 
class Foo { 

private Helper helper = null; 
private Boolean isHelperInstantiated; 
public Helper getHelper() { 
    if (!isHelperInstantiated) { 
     synchronized(this) { 
      if (helper == null) { 
       helper = new Helper(); 
       isHelperInstantiated = true; 
      } 
     } 
    } 
    return helper; 
} 

// other functions and members... 
}  
Các vấn đề liên quan