it-swarm-vi.tech

Bạn có nên đồng bộ hóa phương thức chạy? Tại sao hay tại sao không?

Tôi đã luôn nghĩ rằng việc đồng bộ hóa phương thức chạy trong một lớp Java mà thực hiện Runnable là không cần thiết. Tôi đang cố gắng tìm hiểu tại sao mọi người làm điều này:

public class ThreadedClass implements Runnable{
    //other stuff
    public synchronized void run(){
        while(true)
             //do some stuff in a thread
        }
    }
}

Có vẻ như không cần thiết và không cần thiết vì họ đang lấy khóa của đối tượng cho một luồng khác. Hay đúng hơn, họ đang nói rõ rằng chỉ có một luồng có quyền truy cập vào phương thức run (). Nhưng vì nó là phương thức chạy, nên nó không phải là luồng của chính nó? Vì vậy, chỉ có nó mới có thể tự truy cập và nó không cần một cơ chế khóa riêng biệt?

Tôi đã tìm thấy một đề xuất trực tuyến rằng bằng cách đồng bộ hóa phương thức chạy, bạn có khả năng có thể tạo ra một hàng đợi luồng thực tế bằng cách thực hiện điều này:

 public void createThreadQueue(){
    ThreadedClass a = new ThreadedClass();
    new Thread(a, "First one").start();
    new Thread(a, "Second one, waiting on the first one").start();
    new Thread(a, "Third one, waiting on the other two...").start();
 }

Tôi sẽ không bao giờ làm điều đó cá nhân, nhưng nó cho câu hỏi tại sao mọi người sẽ đồng bộ hóa phương thức chạy.Bất kỳ ý tưởng tại sao hoặc tại sao không nên đồng bộ hóa phương thức chạy?

31
MHP

Đồng bộ hóa phương thức run() của Runnable là hoàn toàn vô nghĩa trừ khi bạn muốn chia sẻ Runnable giữa nhiều luồngbạn muốn tuần tự hóa việc thực hiện các luồng đó. Mà về cơ bản là một mâu thuẫn trong điều khoản.

Về lý thuyết, có một kịch bản phức tạp hơn nhiều trong đó bạn có thể muốn đồng bộ hóa phương thức run(), một lần nữa liên quan đến việc chia sẻ Runnable giữa nhiều luồng nhưng cũng sử dụng wait()notify(). Tôi chưa bao giờ gặp nó trong hơn 21 năm của Java.

28
user207421

Có 1 lợi thế khi sử dụng synchronized void blah() so với void blah() { synchronized(this) { và đó là mã byte kết quả của bạn sẽ ngắn hơn 1 byte, vì đồng bộ hóa sẽ là một phần của chữ ký phương thức thay vì hoạt động. Điều này có thể ảnh hưởng đến cơ hội nội tuyến phương thức bởi trình biên dịch JIT. Khác hơn là không có sự khác biệt.

Tùy chọn tốt nhất là sử dụng private final Object lock = new Object() nội bộ để ngăn người khác có khả năng khóa màn hình của bạn. Nó đạt được kết quả tương tự mà không có nhược điểm của khóa ác bên ngoài. Bạn có thêm byte đó, nhưng nó hiếm khi tạo ra sự khác biệt.

Vì vậy, tôi sẽ nói không, không sử dụng từ khóa synchronized trong chữ ký. Thay vào đó, sử dụng một cái gì đó như

public class ThreadedClass implements Runnable{
    private final Object lock = new Object();

    public void run(){
        synchronized(lock) {
            while(true)
                 //do some stuff in a thread
            }
        }
    }
}

Chỉnh sửa để phản hồi bình luận:

Xem xét những gì đồng bộ hóa làm: nó ngăn các luồng khác vào cùng một khối mã. Vì vậy, hãy tưởng tượng bạn có một lớp học như bên dưới. Giả sử kích thước hiện tại là 10. Ai đó cố gắng thực hiện một add và nó buộc thay đổi kích thước của mảng sao lưu. Trong khi họ đang thay đổi kích thước mảng, ai đó gọi một makeExactSize(5) trên một luồng khác. Bây giờ, đột nhiên bạn đang cố truy cập data[6] và nó đánh bom bạn. Đồng bộ hóa được cho là để ngăn chặn điều đó xảy ra. Trong các chương trình đa luồng, bạn chỉ cần đồng bộ hóa CẦN.

class Stack {
    int[] data = new int[10];
    int pos = 0;

    void add(int inc) {
        if(pos == data.length) {
            int[] tmp = new int[pos*2];
            for(int i = 0; i < pos; i++) tmp[i] = data[i];
            data = tmp;
        }
        data[pos++] = inc;
    }

    int remove() {
        return data[pos--];
    }

    void makeExactSize(int size) {
        int[] tmp = new int[size];
        for(int i = 0; i < size; i++) tmp[i] = data[i];
        data = tmp;
    }
}
2
corsiKa

Tại sao? An toàn tối thiểu thêm và tôi không thấy bất kỳ kịch bản hợp lý nào mà nó sẽ tạo ra sự khác biệt.

Tại sao không? Nó không chuẩn. Nếu bạn đang mã hóa thành một phần của một nhóm, khi một thành viên khác nhìn thấy run được đồng bộ hóa của bạn, anh ta có thể sẽ lãng phí 30 phút để cố gắng tìm ra điều gì đặc biệt với run của bạn hoặc với khung bạn đang sử dụng để chạy Runnable '.

2
toto2

Theo kinh nghiệm của tôi, việc thêm từ khóa "đồng bộ hóa" vào phương thức run () là không hữu ích. Nếu chúng ta cần đồng bộ hóa nhiều luồng hoặc chúng ta cần một hàng đợi an toàn cho luồng, chúng ta có thể sử dụng các thành phần phù hợp hơn, chẳng hạn như ConcurrencyLinkedQueue.

1
James Gan

Về mặt lý thuyết, bạn có thể gọi phương thức chạy mà không gặp vấn đề gì (sau tất cả là công khai). Nhưng điều đó không có nghĩa là người ta nên làm điều đó. Vì vậy, về cơ bản không có lý do để làm điều này, ngoài việc thêm chi phí không đáng kể vào lệnh gọi run (). Chà, ngoại trừ nếu bạn sử dụng ví dụ nhiều lần gọi new Thread - mặc dù tôi không chắc chắn rằng điều đó hợp pháp với API luồng và b) dường như hoàn toàn vô dụng.

Ngoài ra createThreadQueue của bạn không hoạt động. synchronized trên một phương thức không tĩnh đồng bộ hóa trên đối tượng cá thể (tức là this), vì vậy cả ba luồng sẽ chạy song song.

0
Voo

Xem qua các nhận xét và ghi chú mã và chạy các khối khác nhau để thấy rõ sự khác biệt, đồng bộ hóa ghi chú sẽ chỉ có sự khác biệt nếu sử dụng cùng một đối tượng có thể chạy được, nếu mỗi luồng bắt đầu có một mã mới có thể chạy được thì nó sẽ không tạo ra sự khác biệt nào.

class Kat{

public static void main(String... args){
  Thread t1;
  // MyUsualRunnable is usual stuff, only this will allow concurrency
  MyUsualRunnable m0 = new MyUsualRunnable();
  for(int i = 0; i < 5; i++){
  t1 = new Thread(m0);//*imp*  here all threads created are passed the same runnable instance
  t1.start();
  }

  // run() method is synchronized , concurrency killed
  // uncomment below block and run to see the difference

  MySynchRunnable1 m1 = new MySynchRunnable1();
  for(int i = 0; i < 5; i++){
  t1 = new Thread(m1);//*imp*  here all threads created are passed the same runnable instance, m1
  // if new insances of runnable above were created for each loop then synchronizing will have no effect

  t1.start();
}

  // run() method has synchronized block which lock on runnable instance , concurrency killed
  // uncomment below block and run to see the difference
  /*
  MySynchRunnable2 m2 = new MySynchRunnable2();
  for(int i = 0; i < 5; i++){
  // if new insances of runnable above were created for each loop then synchronizing will have no effect
  t1 = new Thread(m2);//*imp*  here all threads created are passed the same runnable instance, m2
  t1.start();
}*/

}
}

class MyUsualRunnable implements Runnable{
  @Override
  public void  run(){
    try {Thread.sleep(1000);} catch (InterruptedException e) {}
}
}

class MySynchRunnable1 implements Runnable{
  // this is implicit synchronization
  //on the runnable instance as the run()
  // method is synchronized
  @Override
  public synchronized void  run(){
    try {Thread.sleep(1000);} catch (InterruptedException e) {}
}
}

class MySynchRunnable2 implements Runnable{
  // this is explicit synchronization
  //on the runnable instance
  //inside the synchronized block
  // MySynchRunnable2 is totally equivalent to MySynchRunnable1
  // usually we never synchronize on this or synchronize the run() method
  @Override
  public void  run(){
    synchronized(this){
    try {Thread.sleep(1000);} catch (InterruptedException e) {}
  }
}
}
0
Anirtak Varma