2009-12-14 37 views
6

Tôi đang cố gắng tìm hiểu xem mã bên dưới có gặp phải bất kỳ vấn đề tương tranh tiềm ẩn nào không. Cụ thể, vấn đề về khả năng hiển thị liên quan đến biến dễ bay hơi. dễ bay hơi được định nghĩa là: Giá trị của biến này sẽ không bao giờ được lưu trữ thread-địa phương: tất cả đọc và viết sẽ đi thẳng vào "bộ nhớ chính"Đồng thời, hiển thị đối tượng

public static void main(String [] args) 
{ 
    Test test = new Test(); 

    // This will always single threaded 
    ExecutorService ex = Executors.newSingleThreadExecutor(); 

    for (int i=0; i<10; ++i) 
     ex.execute(test); 
} 

private static class Test implements Runnable { 
    // non volatile variable in question 
    private int state = 0; 

    @Override 
    public void run() { 
     // will we always see updated state value? Will updating state value 
     // guarantee future run's see the value? 
     if (this.state != -1) 
      this.state++; 
    } 
} 

Đối với trên đơn thi hành di chúc ren:

Có thể thực hiện test.state không dễ bay hơi không? Nói cách khác, sẽ mỗi lần tiếp theo Test.run() (sẽ xuất hiện tuần tự và không đồng thời bởi vì một người thi hành một lần nữa là luồng đơn), luôn luôn xem giá trị được kiểm tra đã cập nhật? Nếu không, không thoát khỏi Test.run() đảm bảo mọi thay đổi được tạo cục bộ sẽ được ghi lại vào bộ nhớ chính? Nếu không thì những thay đổi được thực hiện tại địa phương có được ghi lại cho bộ nhớ chính nếu không khi thoát khỏi chủ đề?

+0

bạn đã nhận được ở đâu định nghĩa đó. Âm thanh như định nghĩa JMM trước 1.5 (không thể thực hiện được). –

+0

http://www.javamex.com/tutorials/synchronization_volatile.shtml – Integer

+0

Điều quan trọng là phải nhận ra rằng khi chuỗi hoàn thành 'Test.run()', luồng không kết thúc và bất kỳ đảm bảo nào về các giá trị được viết bởi thread được flushed đến bộ nhớ chính trước khi nó chấm dứt không áp dụng.Chủ đề phương thức 'run()' gọi 'Test.run()' của bạn chỉ đơn giản là một vòng lặp, cho đến khi nó nhận được một nhiệm vụ mới để thực thi. Khi nhiệm vụ đó trở về từ * phương thức * run() 'của nó, luồng chỉ cho đến khi nhiệm vụ tiếp theo; nó không chấm dứt (và do đó, tuôn ra trạng thái của nó). – erickson

Trả lời

2

Có an toàn, ngay cả khi người thực hiện thay thế chuỗi của nó ở giữa. Chủ đề bắt đầu/chấm dứt cũng là điểm đồng bộ.

http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.4.4

Một ví dụ đơn giản:

static int state; 
static public void main(String... args) { 
    state = 0;     // (1) 
    Thread t = new Thread() { 
     public void run() { 
      state = state + 1; // (2) 
     } 
    }; 
    t.start(); 
    t.join(); 
    System.out.println(state); // (3) 
} 

Nó được đảm bảo rằng (1), (2), (3) được sắp xếp tốt và cư xử như mong đợi.

Đối với người thi hành chủ đề duy nhất, "Nhiệm vụ được đảm bảo để thực hiện tuần tự", nó bằng cách nào đó phải phát hiện các kết thúc của một nhiệm vụ trước khi bắt đầu một kế tiếp, mà nhất thiết phải đúng cách đồng bộ hóa các run() khác nhau của

-1

Nếu ExecutorService của bạn là chuỗi đơn thì không có trạng thái chia sẻ, vì vậy tôi không thấy làm thế nào có thể có bất kỳ vấn đề xung quanh đó.

Tuy nhiên, sẽ không có ý nghĩa hơn khi chuyển một phiên bản mới của lớp Test cho mỗi cuộc gọi đến execute()? tức là

for (int i=0; i<10; ++i) 
    ex.execute(new Test()); 

Bằng cách này sẽ không có trạng thái chia sẻ nào.

+1

Điều đó không có ý nghĩa gì cả, toàn bộ vấn đề của câu hỏi là cùng một đối tượng được sử dụng. – KernelJ

4

Miễn là nó chỉ là một chuỗi duy nhất, không cần phải làm cho nó dễ bay hơi. Nếu bạn định sử dụng nhiều luồng, bạn không nên chỉ sử dụng biến động nhưng đồng bộ hóa quá. Tăng số không phải là hoạt động nguyên tử - đó là quan niệm sai lầm phổ biến.

public void run() { 
    synchronize (this) { 
     if (this.state != -1) 
      this.state++; 
    } 
} 

Thay vì sử dụng đồng bộ hóa, bạn cũng có thể sử dụng AtomicInteger#getAndIncrement() (nếu bạn không cần nếu trước).

private AtomicInteger state = new AtomicInteger(); 

public void run() { 
    state.getAndIncrement() 
} 
+0

Tôi chỉ hỏi một câu hỏi đơn giản về việc liệu các thay đổi trạng thái có được nhìn thấy bởi các luồng khác trong một chế độ luồng đơn và bạn đã trả lời điều đó chưa. Tuy nhiên, việc thoát khỏi chủ đề, tôi không thấy lý do tại sao tôi cần phải sử dụng dễ bay hơi VÀ đồng bộ hóa. Có vẻ như tôi đồng bộ sẽ đủ. – Integer

+0

Tôi chỉ muốn làm cho bạn (và những người khác vấp vào câu hỏi này) nhận thức được thực tế là sử dụng dễ bay hơi để tăng số nguyên không đủ trong môi trường đa luồng. Bạn sẽ luôn luôn cần cả hai, dễ bay hơi và đồng bộ hóa để được an toàn thread. – sfussenegger

+0

Lý do tại sao dễ bay hơi thường không đủ là rõ ràng với tôi: kể từ khi không nguyên tử của ++ một số gia số của các chủ đề có thể không có hiệu lực do điều kiện chủng tộc. Nhưng tại sao nên đồng bộ hóa mà không dễ bay hơi thì không đủ? Nếu bạn đồng bộ hóa lần đọc là tốt, tất nhiên. –

0

Mã của bạn, cụ thể này chút

  if (this.state != -1) 
        this.state++; 

sẽ yêu cầu các nguyên tử kiểm tra giá trị nhà nước và sau đó một tăng đối với nhà nước trong bối cảnh đồng thời. Vì vậy, ngay cả khi biến của bạn là dễ bay hơi và nhiều hơn một chủ đề đã được tham gia, bạn sẽ có vấn đề tương tranh.

Nhưng thiết kế của bạn dựa trên việc khẳng định rằng sẽ luôn chỉ có một ví dụ về Kiểm tra, , ví dụ đơn lẻ đó chỉ được cấp cho một chuỗi (cùng một). (Nhưng lưu ý rằng ví dụ đơn lẻ thực tế là trạng thái chia sẻ giữa chuỗi chính và chuỗi của trình thực thi.)

Tôi nghĩ rằng bạn cần làm cho các giả định này rõ ràng hơn (trong mã, ví dụ, sử dụng ThreadLocal và ThreadLocal) .được()). Điều này là để bảo vệ cả hai chống lại các lỗi trong tương lai (khi một số nhà phát triển khác có thể bất cẩn vi phạm các giả định thiết kế) và bảo vệ chống lại việc đưa ra các giả định về việc triển khai nội bộ của the Executor method you are using.tuần tự và không nhất thiết phải cùng một chuỗi trong mỗi lời gọi thực thi (runnable).

+0

Nghe này, tôi biết mã không phải là tác phẩm nghệ thuật. Tôi hiểu có hàng triệu cải tiến có thể được thực hiện. Có, tôi biết về threadlocals, tôi biết về đồng bộ hóa và biến nguyên tử. Nó không phải là mã sản xuất. Nó được làm mã để minh họa cho câu hỏi của tôi về khả năng hiển thị đồng thời và dễ bay hơi. – Integer

0

Hoàn toàn ổn định đối với trạng thái không dễ bay hơi trong mã cụ thể này, bởi vì chỉ có một chuỗi và chỉ chuỗi đó truy cập trường. Vô hiệu hóa bộ nhớ đệm giá trị của trường này trong chuỗi chỉ bạn có sẽ chỉ cho một lần truy cập hiệu suất.

Tuy nhiên, nếu bạn muốn sử dụng giá trị của nhà nước trong các chủ đề chính mà là chạy vòng lặp, bạn phải thực hiện các lĩnh vực không ổn định:

for (int i=0; i<10; ++i) { 
      ex.execute(test); 
      System.out.println(test.getState()); 
    } 

Tuy nhiên, ngay cả điều này có thể không hoạt động chính xác với không ổn định, vì không có sự đồng bộ giữa các luồng.

Vì trường này là riêng tư, chỉ có vấn đề nếu chuỗi chính thực hiện một phương thức có thể truy cập trường này.

3

Ban đầu, tôi đã suy nghĩ theo cách này:

Nếu nhiệm vụ luôn được thực hiện bởi thread cùng, thì sẽ không có vấn đề . Nhưng Excecutor được sản xuất bởi newSingleThreadExecutor() có thể tạo ra chủ đề mới để thay thế một số mà bị giết vì bất kỳ lý do gì. Không có bảo đảm về thời điểm chủ đề thay thế sẽ được tạo hoặc chủ đề nào sẽ tạo chủ đề đó.

Nếu chuỗi thực hiện một số thao tác ghi, sau đó gọi start() trên một chủ đề mới, những ghi sẽ hiển thị với chủ đề mới. Nhưng không có gì đảm bảo rằng quy tắc đó áp dụng trong trường hợp này là .

Nhưng không thể chối cãi là đúng: tạo đúng ExecutorService mà không có đủ rào cản để đảm bảo khả năng hiển thị thực tế là không thể. Tôi đã quên rằng phát hiện cái chết của sợi khác là mối quan hệ đồng bộ với mối quan hệ. Cơ chế chặn được sử dụng cho các luồng công nhân nhàn rỗi cũng sẽ yêu cầu một rào cản.

+0

"Không có sự đảm bảo về thời điểm tạo chủ đề thay thế hoặc chuỗi sẽ tạo ra chủ đề đó". Cho dù thực thi chạy thử nghiệm trong cùng một chủ đề hoặc chủ đề thay thế, nó có tạo sự khác biệt không? Bởi vì tôi đang sử dụng cùng một thể hiện của Test(). Câu hỏi của tôi là cụ thể cho dù tất cả các hành vi của Test.run() sẽ thấy giá trị trạng thái được cập nhật trước đó. – Integer

+2

Có, có hay không cùng một luồng được sử dụng để chạy 'Kiểm tra' tạo ra tất cả sự khác biệt. Nếu một luồng duy nhất được đảm bảo chạy tất cả các lệnh thực hiện của 'run()', thì nó chỉ là bình thường, lập trình luồng đơn, và các bản cập nhật chắc chắn sẽ được nhìn thấy trong các lời gọi tiếp theo bởi luồng đó. Tuy nhiên, vì tác vụ không sử dụng các rào cản bộ nhớ, các bản cập nhật đó có thể không hiển thị với một chuỗi * khác *, bao gồm một chuỗi thay thế được tạo bởi trình thực thi. – erickson

+0

Cảm ơn bạn. Bạn đã trả lời câu hỏi của tôi một cách hoàn hảo. – Integer

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