Khóa phương thức đồng bộ hóa Java trên đối tượng hay phương thức?


191

Nếu tôi có 2 phương thức được đồng bộ hóa trong cùng một lớp, nhưng mỗi phương thức truy cập các biến khác nhau, 2 luồng có thể truy cập cùng lúc 2 phương thức đó không? Liệu khóa xảy ra trên đối tượng, hoặc nó có được cụ thể như các biến bên trong phương thức được đồng bộ hóa không?

Thí dụ:

class X {

    private int a;
    private int b;

    public synchronized void addA(){
        a++;
    }

    public synchronized void addB(){
        b++;
    }

}

2 luồng có thể truy cập cùng một thể hiện của lớp X đang thực hiện x.addA() và x.addB()cùng một lúc không?

Câu trả lời:


197

Nếu bạn khai báo phương thức là đồng bộ hóa (như bạn đang thực hiện bằng cách nhập public synchronized void addA()), bạn đồng bộ hóa trên toàn bộ đối tượng, do đó hai luồng truy cập vào một biến khác nhau từ cùng một đối tượng này sẽ chặn nhau.

Nếu bạn chỉ muốn đồng bộ hóa trên một biến tại một thời điểm, vì vậy hai luồng sẽ không chặn nhau trong khi truy cập các biến khác nhau, bạn đã đồng bộ hóa chúng riêng rẽ trong synchronized ()các khối. Nếu ablà các tham chiếu đối tượng bạn sẽ sử dụng:

public void addA() {
    synchronized( a ) {
        a++;
    }
}

public void addB() {
    synchronized( b ) {
        b++;
    }
}

Nhưng vì chúng là nguyên thủy nên bạn không thể làm điều này.

Tôi sẽ đề nghị bạn sử dụng AtomicInteger thay thế:

import java.util.concurrent.atomic.AtomicInteger;

class X {

    AtomicInteger a;
    AtomicInteger b;

    public void addA(){
        a.incrementAndGet();
    }

    public void addB(){ 
        b.incrementAndGet();
    }
}

181
Nếu bạn đồng bộ hóa trên phương thức bạn khóa toàn bộ đối tượng, do đó, hai luồng truy cập vào một biến khác nhau từ cùng một đối tượng sẽ chặn nhau. Đó là một chút sai lệch. Đồng bộ hóa trên phương thức có chức năng tương đương với việc có một synchronized (this)khối xung quanh thân phương thức. Đối tượng "này" không bị khóa, thay vào đó, đối tượng "này" được sử dụng làm mutex và cơ thể được ngăn chặn thực thi đồng thời với các phần mã khác cũng được đồng bộ hóa trên "này". Nó không có tác dụng đối với các lĩnh vực / phương pháp khác của "cái này" không được đồng bộ hóa.
Mark Peters

13
Vâng, nó thực sự sai lệch. Ví dụ thực tế - Hãy xem điều này - stackoverflow.com/questions/14447095/ Kẻ - Tóm tắt: Khóa chỉ ở cấp phương thức được đồng bộ hóa và các biến đối tượng của đối tượng có thể được truy cập bởi luồng khác
mac

5
Ví dụ đầu tiên bị phá vỡ cơ bản. Nếu ablà các đối tượng, ví dụ: Integers, bạn đã đồng bộ hóa trong các trường hợp bạn đang thay thế bằng các đối tượng khác nhau khi áp dụng ++toán tử.
Holger

sửa câu trả lời của bạn và khởi tạo AtomicInteger: AtomicInteger a = new AtomicInteger (0);
Mehdi

Có lẽ anwser này nên được cập nhật với lời giải thích trong phần khác về việc đồng bộ hóa trên chính đối tượng: stackoverflow.com/a/10324280/1099452
lucasvc

71

Đồng bộ hóa trên khai báo phương pháp là đường tổng hợp cho việc này:

 public void addA() {
     synchronized (this) {
          a++;
     }
  }

Trên một phương pháp tĩnh, đó là đường cú pháp cho việc này:

 ClassA {
     public static void addA() {
          synchronized(ClassA.class) {
              a++;
          }
 }

Tôi nghĩ rằng nếu các nhà thiết kế Java biết sau đó những gì được hiểu về đồng bộ hóa, thì họ sẽ không thêm đường cú pháp, vì nó thường không dẫn đến việc triển khai đồng thời xấu.


3
Không đúng. phương thức đồng bộ hóa tạo ra mã byte khác với đồng bộ hóa (đối tượng). Trong khi chức năng tương đương, nó không chỉ là đường cú pháp.
Steve Kuo

10
Tôi không nghĩ rằng "đường cú pháp" được định nghĩa đúng như byte-code tương đương. Vấn đề là nó là chức năng tương đương.
Yishai

1
Nếu các nhà thiết kế Java đã biết những gì đã biết về màn hình thì họ sẽ có / nên làm điều đó theo cách khác, thay vì về cơ bản mô phỏng các bộ phận của Unix. Per Brinch Hansen đã nói 'rõ ràng tôi đã làm việc vô ích' khi anh ta nhìn thấy các nguyên hàm đồng thời của Java .
Hầu tước Lorne

Đây là sự thật. Ví dụ được đưa ra bởi OP sẽ xuất hiện để khóa từng phương thức nhưng thực tế tất cả chúng đều khóa trên cùng một đối tượng. Cú pháp rất lừa đảo. Sau khi sử dụng Java hơn 10 năm, tôi không biết điều này. Vì vậy, tôi sẽ tránh các phương pháp đồng bộ vì lý do này. Tôi luôn nghĩ rằng một đối tượng vô hình đã được tạo cho mỗi phương thức được xác định đồng bộ hóa.
Peter Quires 16/03/18

21

Từ "Hướng dẫn Java ™" về các phương thức được đồng bộ hóa :

Đầu tiên, không thể có hai cách gọi các phương thức được đồng bộ hóa trên cùng một đối tượng để xen kẽ. Khi một luồng đang thực thi một phương thức được đồng bộ hóa cho một đối tượng, tất cả các luồng khác gọi các phương thức được đồng bộ hóa cho cùng một khối đối tượng (tạm dừng thực thi) cho đến khi luồng đầu tiên được thực hiện với đối tượng.

Từ "Hướng dẫn Java ™" trên các khối được đồng bộ hóa :

Báo cáo được đồng bộ hóa cũng hữu ích để cải thiện đồng thời với đồng bộ hóa chi tiết. Giả sử, ví dụ, lớp MsLunch có hai trường đối tượng, c1 và c2, không bao giờ được sử dụng cùng nhau. Tất cả các bản cập nhật của các trường này phải được đồng bộ hóa, nhưng không có lý do gì để ngăn bản cập nhật của c1 bị xen kẽ với bản cập nhật của c2 - và làm như vậy sẽ giảm đồng thời bằng cách tạo chặn không cần thiết. Thay vì sử dụng phương pháp đồng bộ hoặc sử dụng khóa liên quan với điều này, chúng ta tạo ra hai đối tượng duy nhất để cung cấp ổ khóa.

(Nhấn mạnh của tôi)

Giả sử bạn có 2 phi đan xen biến. Vì vậy, bạn muốn truy cập từng cái từ một chủ đề khác nhau cùng một lúc. Bạn cần định nghĩa khóa không phải trên chính lớp đối tượng, mà trên lớp Object như bên dưới (ví dụ từ liên kết Oracle thứ hai):

public class MsLunch {

    private long c1 = 0;
    private long c2 = 0;

    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void inc1() {
        synchronized(lock1) {
            c1++;
        }
    }

    public void inc2() {
        synchronized(lock2) {
            c2++;
        }
    }
}

14

Các khóa truy cập là về đối tượng, không phải trên phương pháp này. Những biến nào được truy cập trong phương thức là không liên quan.

Thêm "đồng bộ hóa" vào phương thức có nghĩa là luồng chạy mã phải thu được khóa trên đối tượng trước khi tiếp tục. Thêm "đồng bộ hóa tĩnh" có nghĩa là luồng chạy mã phải có được khóa trên đối tượng lớp trước khi tiếp tục. Ngoài ra, bạn có thể bọc mã trong một khối như thế này:

public void addA() {
    synchronized(this) {
        a++;
    }
}

để bạn có thể xác định các đối tượng có khóa phải được mua lại.

Nếu bạn muốn tránh khóa đối tượng chứa, bạn có thể chọn giữa:


7

Từ liên kết tài liệu oracle

Làm cho các phương thức được đồng bộ hóa có hai tác dụng:

Đầu tiên, không thể có hai yêu cầu của các phương thức được đồng bộ hóa trên cùng một đối tượng để xen kẽ. Khi một luồng đang thực thi một phương thức được đồng bộ hóa cho một đối tượng, tất cả các luồng khác gọi các phương thức được đồng bộ hóa cho cùng một khối đối tượng (tạm dừng thực thi) cho đến khi luồng đầu tiên được thực hiện với đối tượng.

Thứ hai, khi một phương thức được đồng bộ hóa thoát ra, nó sẽ tự động thiết lập mối quan hệ xảy ra trước khi có bất kỳ lời gọi tiếp theo nào của một phương thức được đồng bộ hóa cho cùng một đối tượng. Điều này đảm bảo rằng các thay đổi về trạng thái của đối tượng được hiển thị cho tất cả các luồng

Hãy xem trang tài liệu này để hiểu các khóa nội tại và hành vi khóa.

Điều này sẽ trả lời câu hỏi của bạn: Trên cùng một đối tượng x, bạn không thể gọi x.addA () và x.addB () cùng lúc khi một trong các thực thi phương thức được đồng bộ hóa đang diễn ra.


4

Nếu bạn có một số phương thức không được đồng bộ hóa và đang truy cập và thay đổi các biến thể hiện. Trong ví dụ của bạn:

 private int a;
 private int b;

bất kỳ số lượng luồng có thể truy cập vào các phương pháp không đồng bộ cùng một lúc khi thread khác là trong phương pháp đồng bộ của cùng một đối tượng và có thể thay đổi các biến ví dụ. Ví dụ: -

 public void changeState() {
      a++;
      b++;
    }

Bạn cần tránh kịch bản rằng các phương thức không được đồng bộ hóa đang truy cập vào các biến thể hiện và thay đổi nó nếu không sẽ không sử dụng các phương thức được đồng bộ hóa.

Trong kịch bản dưới đây: -

class X {

        private int a;
        private int b;

        public synchronized void addA(){
            a++;
        }

        public synchronized void addB(){
            b++;
        }
     public void changeState() {
          a++;
          b++;
        }
    }

Chỉ một trong các luồng có thể ở phương thức addA hoặc addB nhưng đồng thời, bất kỳ số lượng luồng nào cũng có thể nhập phương thức changeState. Không có hai luồng nào có thể nhập addA và addB cùng một lúc (vì khóa Cấp đối tượng) nhưng đồng thời, bất kỳ số lượng luồng nào cũng có thể nhập changeState.


3

Bạn có thể làm một cái gì đó như sau. Trong trường hợp này, bạn đang sử dụng khóa trên a và b để đồng bộ hóa thay vì khóa trên "này". Chúng tôi không thể sử dụng int vì các giá trị nguyên thủy không có khóa, vì vậy chúng tôi sử dụng Integer.

class x{
   private Integer a;
   private Integer b;
   public void addA(){
      synchronized(a) {
         a++;
      }
   }
   public synchronized void addB(){
      synchronized(b) {
         b++;
      }
   }
}

3

Có, nó sẽ chặn phương thức khác vì phương thức được đồng bộ hóa áp dụng cho đối tượng lớp WHOLE như đã chỉ .... nhưng dù sao nó sẽ chặn CHỈ thực thi luồng khác trong khi thực hiện tổng trong bất kỳ phương thức nào addA hoặc addB mà nó nhập, bởi vì khi nó kết thúc ... một luồng sẽ MIỄN PHÍ đối tượng và luồng khác sẽ truy cập vào phương thức khác và cứ thế hoạt động hoàn hảo.

Tôi có nghĩa là "đồng bộ hóa" được thực hiện chính xác để chặn luồng khác truy cập vào luồng khác trong khi thực thi mã cụ thể. VÌ VẬY MÃ SỐ NÀY SILL LÀM VIỆC.

Lưu ý cuối cùng, nếu có biến 'a' và 'b', không chỉ là biến duy nhất 'a' hay bất kỳ tên nào khác, thì không cần phải đồng bộ hóa phương thức này vì nó hoàn toàn an toàn khi sử dụng var khác (Bộ nhớ khác vị trí).

class X {

private int a;
private int b;

public void addA(){
    a++;
}

public void addB(){
    b++;
}}

Sẽ làm việc tốt


2

Ví dụ này (mặc dù không đẹp) có thể cung cấp cái nhìn sâu sắc hơn về cơ chế khóa. Nếu gia tăngA được đồng bộ hóagia tăngB không đồng bộ , sau đó incrementB sẽ được thực hiện càng sớm càng tốt, nhưng nếu incrementB cũng được đồng bộ thì nó phải 'chờ' cho incrementA đến cuối, trước khi incrementB có thể làm công việc của mình.

Cả hai phương thức đều được gọi vào một cá thể - đối tượng, trong ví dụ này là: công việc và các chủ đề 'cạnh tranh' là aThreadchính .

Hãy thử với 'được đồng bộ hóa ' trong gia sốB và không có nó và bạn sẽ thấy các kết quả khác nhau. Nếu gia tăngB được ' đồng bộ hóa ' thì nó phải chờ tăngA () để kết thúc. Chạy nhiều lần mỗi biến thể.

class LockTest implements Runnable {
    int a = 0;
    int b = 0;

    public synchronized void incrementA() {
        for (int i = 0; i < 100; i++) {
            this.a++;
            System.out.println("Thread: " + Thread.currentThread().getName() + "; a: " + this.a);
        }
    }

    // Try with 'synchronized' and without it and you will see different results
    // if incrementB is 'synchronized' as well then it has to wait for incrementA() to finish

    // public void incrementB() {
    public synchronized void incrementB() {
        this.b++;
        System.out.println("*************** incrementB ********************");
        System.out.println("Thread: " + Thread.currentThread().getName() + "; b: " + this.b);
        System.out.println("*************** incrementB ********************");
    }

    @Override
    public void run() {
        incrementA();
        System.out.println("************ incrementA completed *************");
    }
}

class LockTestMain {
    public static void main(String[] args) throws InterruptedException {
        LockTest job = new LockTest();
        Thread aThread = new Thread(job);
        aThread.setName("aThread");
        aThread.start();
        Thread.sleep(1);
        System.out.println("*************** 'main' calling metod: incrementB **********************");
        job.incrementB();
    }
}

1

Trong đồng bộ hóa java, nếu một luồng muốn nhập vào phương thức đồng bộ hóa, nó sẽ thu được khóa trên tất cả các phương thức được đồng bộ hóa của đối tượng đó, không chỉ trên một phương thức được đồng bộ hóa mà luồng đang sử dụng. Vì vậy, một luồng thực thi addA () sẽ thu được khóa trên addA () và addB () vì cả hai đều được đồng bộ hóa. Vì vậy, các luồng khác có cùng đối tượng không thể thực thi addB ().


0

Điều này có thể không hoạt động vì quyền anh và hộp số tự động từ Integer sang int và viceversa phụ thuộc vào JVM và có khả năng cao là hai số khác nhau có thể được băm vào cùng một địa chỉ nếu chúng nằm trong khoảng từ -128 đến 127.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.