2012-01-11 15 views
8

Câu hỏi liên quan đến JMM và ngữ nghĩa liên quan đến các trường dễ bay hơi được ghi vào khối được đồng bộ hóa, nhưng đọc không đồng bộ.Sự cần thiết của mảng biến động viết trong khi trong khối đồng bộ

Trong phiên bản ban đầu của mã bên dưới, tôi đã không đồng bộ hóa quyền truy cập vì nó không cần thiết cho các yêu cầu trước đó (và lạm dụng tự gán this.cache = this.cache đảm bảo ngữ nghĩa viết dễ bay hơi). Một số yêu cầu đã thay đổi, cần phải đồng bộ hóa để đảm bảo rằng các bản cập nhật trùng lặp không được gửi đi. Câu hỏi tôi có là khối đồng bộ hóa có ngăn cản việc gán tự động của trường biến động không?

// Cache of byte[] data by row and column. 
    private volatile byte[][][] cache; 

    public byte[] getData(int row, int col) 
    { 
    return cache[row][col]; 
    } 

    public void updateData(int row, int col, byte[] data) 
    { 
    synchronized(cache) 
    { 
     if (!Arrays.equals(data,cache[row][col])) 
     { 
     cache[row][col] = data; 

     // Volatile write. 
     // The below line is intentional to ensure a volatile write is 
     // made to the array, since access via getData is unsynchronized. 
     this.cache = this.cache; 

     // Notification code removed 
     // (mentioning it since it is the reason for synchronizing). 
     } 
    } 
    } 

Không cần đồng bộ hóa, tôi cho rằng viết tự động viết dễ bay hơi là cần thiết về mặt kỹ thuật (mặc dù IDE đánh dấu nó là không có hiệu lực). Với khối đồng bộ, tôi nghĩ rằng nó vẫn còn cần thiết (kể từ khi đọc là không đồng bộ), nhưng tôi chỉ muốn xác nhận vì nó có vẻ vô lý trong mã nếu nó không thực sự cần thiết. Tôi không chắc chắn nếu có bất kỳ đảm bảo rằng tôi không biết giữa kết thúc của một khối đồng bộ và đọc dễ bay hơi.

Trả lời

4

Có, bạn vẫn cần ghi dễ bay hơi, theo Mô hình bộ nhớ Java. Không có thứ tự đồng bộ hóa nào từ việc mở khóa cache đến lần đọc biến động tiếp theo của cache: mở khóa -> volatileRead không đảm bảo khả năng hiển thị. Bạn cần mở khóa -> khóa hoặc volatileWrite -> volatileRead.

Tuy nhiên, các JVM thực có bảo đảm bộ nhớ mạnh hơn nhiều. Thông thường, mở khóavolatileWrite có cùng hiệu ứng bộ nhớ (ngay cả khi chúng có các biến khác nhau); giống như khóavolatileĐọc.

Vì vậy, chúng tôi gặp khó khăn ở đây.Các khuyến nghị điển hình là bạn nên tuân thủ nghiêm ngặt các thông số kỹ thuật. Trừ khi bạn có kiến ​​thức rất rộng về vấn đề này. Ví dụ, một mã JDK có thể sử dụng một số thủ thuật không đúng về mặt lý thuyết; nhưng mã đang nhắm mục tiêu một JVM cụ thể và tác giả là một chuyên gia.

Các chi phí tương đối của phụ write ổn định dường như không phải là lớn anyway.

Mã của bạn là chính xác và hiệu quả; tuy nhiên nó nằm ngoài các mẫu điển hình; Tôi sẽ tinh chỉnh nó một chút như:

private final byte[][][] cacheF = new ...; // dimensions fixed? 
    private volatile byte[][][] cacheV = cacheF; 

    public byte[] getData(int row, int col) 
    { 
    return cacheV[row][col]; 
    } 

    public void updateData(int row, int col, byte[] data) 
    { 
    synchronized(cacheF) 
    { 
     if (!Arrays.equals(data,cacheF[row][col])) 
     { 
     cacheF[row][col] = data; 

     cacheV = cacheF; 
     } 
    } 
    } 
+0

Cảm ơn bạn đã trả lời, rõ ràng và xác nhận những gì tôi nghi ngờ. Các lời khuyên liên quan đến việc có hai biến bộ nhớ cache cũng có lẽ là một ý tưởng tốt vì nó tránh có ai đó loại bỏ việc tự gán (mặc dù ý kiến ​​cảnh báo không). –

3

Việc gán tự đảm bảo rằng một chuỗi khác sẽ đọc tham chiếu mảng đã được đặt và không tham chiếu mảng khác. Nhưng bạn có thể có một chủ đề sửa đổi mảng trong khi một luồng khác đọc nó.

Cả hai lần đọc và ghi vào mảng phải được đồng bộ hóa. Ngoài ra, tôi sẽ không mù quáng lưu trữ và trả về mảng đến/từ bộ nhớ cache. Một mảng là một cấu trúc dữ liệu không thể thay đổi, không có chủ đề và bất kỳ luồng nào có thể làm hỏng bộ nhớ cache bằng cách thay đổi mảng. Bạn nên cân nhắc việc tạo các bản sao phòng thủ và/hoặc trả về một danh sách không thể sửa đổi thay vì một mảng byte.

+0

Cảm ơn bạn những lời khuyên, và tôi đồng ý rằng trong một thế giới lý tưởng, bản sao phòng thủ sẽ là một điều tốt để thực hiện, nhưng các mảng được lưu trữ ở đây sẽ không được sửa đổi để hiệu suất đạt được không làm cho bản sao là thích hợp hơn (và nếu điều đó thay đổi bộ nhớ cache có thể được bao bọc bởi một thực hiện trả về bản sao phòng thủ). Tôi chắc chắn rằng mã được viết là chính xác, tôi không chắc liệu tôi có thể xóa bài tập dễ bay hơi hay không bây giờ tôi đã thêm phần đồng bộ hóa (theo nhận xét của tôi về câu trả lời khác, tôi tin rằng tôi vẫn cần nó) muốn xác minh điều đó). –

+0

+1 cho câu trả lời, nhưng tôi chấp nhận câu trả lời khác vì nó trả lời trực tiếp câu hỏi. –

3

Chỉ mục ghi vào một mảng biến động thực sự không có hiệu ứng bộ nhớ. Tức là, nếu bạn đã khởi tạo mảng, có trường được khai báo là dễ bay hơi sẽ không cung cấp cho bạn ngữ nghĩa bộ nhớ mà bạn đang tìm kiếm khi gán cho các phần tử trong mảng.

Nói cách khác

private volatile byte[][]cache = ...; 
cache[row][col] = data; 

Has ngữ nghĩa bộ nhớ giống như

private final byte[][]cache = ...; 
cache[row][col] = data; 

Bởi vì điều này, bạn phải đồng bộ hóa trên tất cả đọc và ghi vào mảng. Và khi tôi nói 'cùng ngữ nghĩa về bộ nhớ' tôi có nghĩa là không có gì đảm bảo rằng các chủ đề sẽ đọc giá trị cập nhật nhất của cache[row][col]

+0

Tôi có cảm giác tương tự, nhưng sau đó anh ta viết cho mảng biến động, và vì biến động có "hiệu ứng xếp tầng", trên mọi thứ được viết trước khi ghi vào trường biến động, tôi nghĩ không có vấn đề về khả năng hiển thị, . Mặc dù nó rất mỏng manh và sẽ rõ ràng hơn và mạnh mẽ hơn với một getData được đồng bộ hóa. –

+1

Vấn đề là mặc dù không có mối quan hệ xảy ra trước đó giữa cửa hàng không dễ bay hơi và kho lưu trữ dễ bay hơi của this.cache = this.cache –

+1

Có một, vì ghi không ổn định vào bộ nhớ cache [row] [col] được tạo trước khi ghi vào bộ nhớ cache, trong cùng một luồng. Và ghi dễ bay hơi xảy ra trước khi đọc bộ nhớ cache. –

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