2010-10-27 31 views
10

Câu hỏi của tôi liên quan xuất bản an toàn giá trị trường trong Java (như được đánh dấu ở đây Java multi-threading & Safe Publication).Xuất bản an toàn khi giá trị được đọc theo các phương thức đã đồng bộ

Theo tôi được biết, một lĩnh vực có thể được đọc một cách an toàn (có nghĩa là truy cập từ nhiều chủ đề sẽ thấy được giá trị đúng) nếu:

  • đọc và ghi được đồng bộ hóa trên màn hình cùng một
  • lĩnh vực là cuối cùng
  • lĩnh vực có nhiều biến động

Nếu hiểu biết của tôi là đúng lớp sau nên không được thread-safe từ giá trị ban đầu được viết mà không có những đặc điểm này. Tuy nhiên tôi thấy khó tin rằng tôi cần phải thực hiện firstvolatile mặc dù chỉ truy cập từ phương thức synchronized.

public class Foo { 

    private boolean needsGreeting = true; 

    public synchronized void greet() { 
     if (needsGreeting) { 
      System.out.println("hello"); 
      needsGreeting = false; 
     } 
    } 
} 

Tôi có thiếu gì đó không? Có mã trên đúng không và nếu có thì tại sao? Hoặc là cần thiết trong các trường hợp như vậy để tạo firstvolatile hoặc sử dụng final AtomicBoolean hoặc một số thứ như thế ngoài để truy cập nó từ phương thức synchronized.

(Chỉ cần làm rõ, tôi biết, rằng nếu các giá trị ban đầu được viết bằng một phương pháp synchronized, nó sẽ là thread-safe thậm chí không có từ khóa volatile.)

Trả lời

3

Nói đúng ra, tôi không thể nhìn thấy có thể giả định rằng, needsGreeting được đặt thành true, khi greet được gọi.

Để điều này đúng, sẽ phải xảy ra trước khi quan hệ giữa lần ghi đầu tiên (xảy ra khi đối tượng được xây dựng) và lần đọc đầu tiên (trong phương thức greet -method). Chapter 17 Threads and Locks trong tuy nhiên JLS, khẳng định sau đây về xảy ra-trước (hb) hạn chế:

17.4.5 Happens-trước khi tự Hai hành động có thể được sắp xếp theo một xảy ra-trước khi mối quan hệ. Nếu một hành động xảy ra trước một hành động khác, thì hành động đầu tiên sẽ hiển thị và được sắp xếp trước thứ hai.

Nếu chúng tôi có hai hành động x và y, chúng tôi viết hb (x, y) để cho biết rằng x xảy ra trước y.

  • Nếu x và y là các hành động của cùng một chủ đề và x đứng trước y theo thứ tự chương trình, sau đó hb (x, y).
  • Có một điểm xảy ra trước khi kết thúc của một hàm tạo của đối tượng đến điểm bắt đầu của trình kết thúc (§12.6) cho đối tượng đó.
  • Nếu một hành động x đồng bộ hóa với một hành động sau y, thì chúng tôi cũng có hb (x, y).
  • Nếu hb (x, y)hb (y, z), sau đó hb (x, z).

Bên cạnh đó, cách duy nhất để giới thiệu một đồng bộ với các mối quan hệ, có nghĩa là, một trật tự đồng bộ là để làm một cái gì đó trong các cách sau:

hành động đồng bộ hóa gây ra sự đồng bộ -với mối quan hệ về hành động, được xác định như sau:

  • Hành động mở khóa trên màn hình m đồng bộ hóa với tất cả các hành động khóa tiếp theo trên m (nơi tiếp theo được xác định theo thứ tự đồng bộ hóa).
  • Ghi vào biến dễ bay hơi (§8.3.1.4) v đồng bộ hóa với tất cả các lần đọc tiếp theo của v bởi bất kỳ chuỗi nào (trong đó tiếp theo được xác định theo thứ tự đồng bộ hóa).
  • Hành động bắt đầu một chuỗi đồng bộ hóa - với hành động đầu tiên trong chuỗi bắt đầu.
  • Việc ghi giá trị mặc định (không, sai hoặc null) cho mỗi biến đồng bộ hóa - với hành động đầu tiên trong mỗi chuỗi. Mặc dù có vẻ hơi lạ khi viết một giá trị mặc định cho một biến trước khi đối tượng chứa biến được cấp phát, khái niệm mỗi đối tượng được tạo ở đầu chương trình với các giá trị khởi tạo mặc định của nó.
  • Hành động cuối cùng trong luồng T1 đồng bộ hóa - với bất kỳ hành động nào trong một luồng T2 khác phát hiện thấy T1 đã kết thúc. T2 có thể thực hiện điều này bằng cách gọi T1.isAlive() hoặc T1.join().
  • Nếu luồng T1 ngắt luồng T2, ngắt bằng T1 đồng bộ - với bất kỳ điểm nào mà bất kỳ luồng nào khác (bao gồm T2) xác định T2 bị gián đoạn (bằng cách ngắt InterruptedException hoặc bằng cách gọi Thread.interrupted hoặc Thread.isInterrupted) .

Nó nói đâu rằng "việc xây dựng một đối tượng xảy ra trước khi bất kỳ cuộc gọi đến các phương pháp trên đối tượng. Các xảy ra-trước khi mối quan hệ tuy nhiên, khẳng định rằng có một xảy ra-trước khi cạnh từ cuối cùng của một hàm khởi tạo của một đối tượng để bắt đầu một trình kết thúc (§12.6) cho đối tượng đó, trong đó có thể là một gợi ý về việc có không phải là một cạnh xảy ra trước khi kết thúc một hàm tạo của đối tượng để bắt đầu một phương pháp tùy ý!

+0

"[...] loại này ngụ ý rằng không có cạnh nào xảy ra trước khi kết thúc của một hàm tạo của đối tượng để bắt đầu một phương pháp tùy ý!". "Hàm ý" này không chính xác. – Grodriguez

+0

Ồ, chắc chắn, nó không phải là một ý nghĩa chính thức. Tuy nhiên, thực tế là họ nói rằng kết thúc của hàm tạo xảy ra trước khi bắt đầu phương thức finalizer, chúng ta sẽ nói "gợi ý" rằng điều này có thể không đúng cho các phương thức tùy ý. – aioobe

+0

@aioobe không, đó là chỉ có cho các trường hợp như mã sau đây: 'new SomeObject()', tức là gọi hàm tạo nhưng không lưu trữ tham chiếu. Sau đó, các đối tượng ngay lập tức có thể được thu thập rác và finalizer ngay lập tức có thể được gọi. Câu bạn đang đề cập đến chỉ cần đảm bảo rằng hàm tạo vẫn kết thúc trước khi trình finalizer chạy. –

4

Không có mối quan hệ xảy ra trước khi kết thúc constructor và phương thức invocations, và như vậy có thể cho một thread bắt đầu xây dựng cá thể và làm cho tham chiếu có sẵn và cho một luồng khác có được tham chiếu đó và bắt đầu gọi() phương pháp trên một đối tượng được xây dựng một phần. Việc đồng bộ hóa trong greet() không thực sự giải quyết vấn đề đó.

Nếu bạn xuất bản một thể hiện qua mẫu khóa được kiểm tra kép được đánh dấu, sẽ dễ dàng hơn để xem cách thực hiện. Nếu có mối quan hệ xảy ra - trước đó, nó phải an toàn ngay cả khi DCLP được sử dụng.

public class Foo { 
    private boolean needsGreeting = true; 

    public synchronized void greet() { 
     if (needsGreeting) { 
      System.out.println("Hello."); 
      needsGreeting = false; 
     } 
    } 
} 

class FooUser { 
    private static Foo foo; 

    public static Foo getFoo() { 
     if (foo == null) { 
      synchronized (FooUser.class) { 
       if (foo == null) { 
        foo = new Foo(); 
       } 
      } 
     } 
     return foo; 
    } 
} 

Nếu có nhiều chủ đề gọi FooUser.getFoo().greet() cùng một lúc, một thread có thể đang xây dựng thể hiện Foo, nhưng một luồng khác có thể tìm thấy một tham chiếu Foo không null sớm, và gọi greet() và tìm nhu cầuGreeting vẫn là false.

Ví dụ về điều này được đề cập trong Java Concurrency in Practice (3.5).

+0

Vì vậy, điều này là đúng, mặc dù việc gán cho 'foo' được thực hiện * sau khi *' new Foo() 'đã được đánh giá đầy đủ? Bạn có một tài liệu tham khảo xác nhận điều này? – aioobe

+0

Việc gán cho foo được thực hiện sau khi hàm Foo() mới được gọi * chỉ * trong chuỗi đó mà nó được thực thi. Từ quan điểm của chủ đề khác, nó có thể không (hoặc có thể không xuất hiện). Các trình biên dịch và bộ vi xử lý được phép sắp xếp lại các lệnh thực hiện và không được thay đổi bằng cách miễn là nó nhất quán trong chuỗi trong đó mã được thực hiện. Đó là những gì tầm nhìn là tất cả về sau khi tất cả. Và đó là lý do tại sao DCLP bị hỏng: khả năng hiển thị. Phần Mô hình bộ nhớ Java của thông số ngôn ngữ sẽ là một tham chiếu tốt. Khả năng hiển thị chính xác chỉ được cung cấp với mối quan hệ xảy ra trước đó. – sjlee

+0

Điều này cũng có thể hữu ích: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html – sjlee

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