Có lợi thế nào khi sử dụng Phương thức được đồng bộ hóa thay vì Khối được đồng bộ hóa không?


401

Có ai có thể cho tôi biết lợi thế của phương thức được đồng bộ hóa so với khối được đồng bộ hóa với một ví dụ không?




1
@cletus câu hỏi này hoàn toàn khác với stackoverflow.com/questions/442564/
Yukio Fukuzawa

Câu trả lời:


431

Ai đó có thể cho tôi biết lợi thế của phương thức được đồng bộ hóa so với khối được đồng bộ hóa với một ví dụ không? Cảm ơn.

Không có một lợi thế rõ ràng của việc sử dụng phương pháp đồng bộ hóa trên khối.

Có lẽ điều duy nhất (nhưng tôi sẽ không gọi đó là lợi thế) là bạn không cần đưa vào tham chiếu đối tượng this.

Phương pháp:

public synchronized void method() { // blocks "this" from here.... 
    ...
    ...
    ...
} // to here

Khối:

public void method() { 
    synchronized( this ) { // blocks "this" from here .... 
        ....
        ....
        ....
    }  // to here...
}

Xem? Không có lợi thế nào cả.

Khối làm có ưu điểm so với các phương pháp tuy nhiên, hầu hết là ở sự linh hoạt bởi vì bạn có thể sử dụng đối tượng khác như khóa trong khi đồng bộ phương pháp này sẽ khóa toàn bộ đối tượng.

Đối chiếu:

// locks the whole object
... 
private synchronized void someInputRelatedWork() {
    ... 
}
private synchronized void someOutputRelatedWork() {
    ... 
}

so với

// Using specific locks
Object inputLock = new Object();
Object outputLock = new Object();

private void someInputRelatedWork() {
    synchronized(inputLock) { 
        ... 
    } 
}
private void someOutputRelatedWork() {
    synchronized(outputLock) { 
        ... 
    }
}

Ngoài ra, nếu phương thức phát triển, bạn vẫn có thể tách riêng phần được đồng bộ hóa:

 private void method() {
     ... code here
     ... code here
     ... code here
    synchronized( lock ) { 
        ... very few lines of code here
    }
     ... code here
     ... code here
     ... code here
     ... code here
}

44
Một lợi ích cho người tiêu dùng API là sử dụng từ khóa được đồng bộ hóa trong khai báo phương thức cũng tuyên bố rõ ràng rằng phương thức đồng bộ hóa trên thể hiện đối tượng và (có lẽ là) an toàn cho luồng.
Scrubbie

59
Tôi biết đây là một câu hỏi cũ nhưng đồng bộ hóa trên "này" được coi trong một số vòng tròn là một mô hình chống. Hậu quả không lường trước là bên ngoài lớp ai đó có thể khóa tham chiếu đối tượng bằng "cái này" và ngăn các luồng khác vượt qua các rào cản trong lớp có khả năng tạo ra tình huống bế tắc. Tạo một "Đối tượng cuối cùng riêng tư = đối tượng mới ();" biến hoàn toàn cho mục đích khóa là giải pháp thường được sử dụng. Đây là một câu hỏi khác liên quan trực tiếp đến vấn đề này.
justin.hughey

30
"trong khi đồng bộ hóa phương thức sẽ khóa lớp hoàn chỉnh." Điều này LAF không đúng. Nó không khóa lớp hoàn chỉnh, nhưng ví dụ hoàn chỉnh. Nhiều đối tượng từ cùng một lớp giữ tất cả khóa riêng của họ. :) Greets
codepleb

4
Một điều thú vị về điều này là việc sử dụng một phương thức được đồng bộ hóa sẽ khiến mã byte được tạo ra có ít hơn 1 lệnh, vì các phương thức có một bit được đồng bộ hóa vào chữ ký của chúng. Vì độ dài của mã byte là một yếu tố trong việc liệu một phương thức có được xếp hàng hay không, việc di chuyển khối sang chữ ký phương thức có thể là sự khác biệt trong quyết định. Trong lý thuyết nào. Tôi sẽ không đưa ra quyết định thiết kế cho một lệnh mã byte đơn được lưu, đó có vẻ là một ý tưởng tồi tệ. Nhưng vẫn một sự khác biệt. =)
corsiKa

2
@corsiKa: bạn lưu nhiều hơn một hướng dẫn. Một synchronizedkhối được thực hiện sử dụng hai hướng dẫn, monitorentermonitorexit, cộng với một bộ xử lý ngoại lệ mà đảm bảo rằng monitorexitđược gọi là ngay cả trong những trường hợp đặc biệt. Đó là tất cả được lưu khi sử dụng một synchronizedphương pháp.
Holger

139

Sự khác biệt thực sự duy nhất là một khối được đồng bộ hóa có thể chọn đối tượng mà nó đồng bộ hóa trên đó. Một phương thức được đồng bộ hóa chỉ có thể sử dụng 'this'(hoặc thể hiện Class tương ứng cho một phương thức lớp được đồng bộ hóa). Ví dụ: đây là tương đương về mặt ngữ nghĩa:

synchronized void foo() {
  ...
}

void foo() {
    synchronized (this) {
      ...
    }
}

Cái sau linh hoạt hơn vì nó có thể cạnh tranh cho khóa liên quan của bất kỳ đối tượng nào , thường là biến thành viên. Nó cũng chi tiết hơn vì bạn có thể thực thi mã đồng thời trước và sau khối nhưng vẫn nằm trong phương thức. Tất nhiên, bạn có thể dễ dàng sử dụng một phương thức được đồng bộ hóa bằng cách tái cấu trúc mã đồng thời thành các phương thức không đồng bộ hóa riêng biệt. Sử dụng bất cứ điều gì làm cho mã dễ hiểu hơn.


Cái sau cũng có thể có công nếu không phải tất cả các mã trong foo () cần được đồng bộ hóa.
Evan

1
Điều này là đúng, nhưng không phải là những gì "Chiến binh" đã hỏi: "Ưu điểm của phương pháp đồng bộ hóa" là không có.
OscarRyz

76

Phương pháp đồng bộ hóa

Ưu điểm:

  • IDE của bạn có thể chỉ ra các phương thức được đồng bộ hóa.
  • Cú pháp nhỏ gọn hơn.
  • Buộc chia các khối được đồng bộ hóa thành các phương thức riêng biệt.

Nhược điểm:

  • Đồng bộ hóa với điều này và do đó, người ngoài cũng có thể đồng bộ hóa với nó.
  • Việc di chuyển mã bên ngoài khối được đồng bộ hóa sẽ khó hơn.

Khối đồng bộ hóa

Ưu điểm:

  • Cho phép sử dụng một biến riêng cho khóa và do đó buộc khóa phải ở trong lớp.
  • Các khối đồng bộ hóa có thể được tìm thấy bằng cách tìm kiếm các tham chiếu đến biến.

Nhược điểm:

  • Cú pháp phức tạp hơn và do đó làm cho mã khó đọc hơn.

Cá nhân tôi thích sử dụng các phương thức được đồng bộ hóa với các lớp chỉ tập trung vào điều cần đồng bộ hóa. Lớp như vậy nên càng nhỏ càng tốt và vì vậy nó sẽ dễ dàng để xem lại sự đồng bộ hóa. Những người khác không cần quan tâm đến việc đồng bộ hóa.


Khi bạn nói "ở trong lớp", bạn có nghĩa là "ở trong đối tượng ", hay tôi đang thiếu thứ gì đó?
OldPeculier

36

Sự khác biệt chính là nếu bạn sử dụng một khối được đồng bộ hóa, bạn có thể khóa trên một đối tượng khác với điều này cho phép linh hoạt hơn nhiều.

Giả sử bạn có một hàng đợi tin nhắn và nhiều nhà sản xuất và người tiêu dùng tin nhắn. Chúng tôi không muốn các nhà sản xuất can thiệp lẫn nhau, nhưng người tiêu dùng sẽ có thể truy xuất tin nhắn mà không phải chờ nhà sản xuất. Vì vậy, chúng tôi chỉ cần tạo ra một đối tượng

Object writeLock = new Object();

Và kể từ bây giờ mỗi khi nhà sản xuất muốn thêm một thông điệp mới, chúng tôi chỉ khóa vào đó:

synchronized(writeLock){
  // do something
}

Vì vậy, người tiêu dùng vẫn có thể đọc, và nhà sản xuất sẽ bị khóa.


2
Ví dụ của bạn được giới hạn để đọc không phá hủy. Nếu việc đọc loại bỏ thông báo khỏi hàng đợi thì điều này sẽ thất bại nếu nó được thực hiện vào một lúc nào đó khi nhà sản xuất ghi vào hàng đợi.
ceving

30

Phương pháp đồng bộ hóa

Phương pháp đồng bộ có hai hiệu ứng.
Đầu tiên, 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 những thay đổi về trạng thái của đối tượng được hiển thị cho tất cả các luồng.

Lưu ý rằng các hàm tạo không thể được đồng bộ hóa - sử dụng từ khóa được đồng bộ hóa với hàm tạo là một lỗi cú pháp. Đồng bộ hóa các hàm tạo không có ý nghĩa gì, bởi vì chỉ luồng tạo ra một đối tượng mới có quyền truy cập vào nó trong khi nó đang được xây dựng.

Báo cáo đồng bộ hóa

Không giống như các phương thức được đồng bộ hóa, các câu lệnh được đồng bộ hóa phải chỉ định đối tượng cung cấp khóa nội tại: Thông thường tôi sử dụng điều này để đồng bộ hóa quyền truy cập vào danh sách hoặc bản đồ nhưng tôi không muốn chặn quyền truy cập vào tất cả các phương thức của đối tượng.

Q: Khóa nội bộ và Đồng bộ hóa đồng bộ hóa được xây dựng xung quanh một thực thể bên trong được gọi là khóa nội tại hoặc khóa màn hình. (Đặc tả API thường đề cập đến thực thể này đơn giản là "màn hình".) Khóa nội tại đóng vai trò trong cả hai khía cạnh của đồng bộ hóa: thực thi quyền truy cập độc quyền vào trạng thái của đối tượng và thiết lập các mối quan hệ xảy ra trước khi cần thiết.

Mỗi đối tượng có một khóa nội tại liên quan đến nó. Theo quy ước, một luồng cần truy cập độc quyền và nhất quán vào các trường của đối tượng phải có được khóa nội tại của đối tượng trước khi truy cập vào chúng, và sau đó giải phóng khóa nội tại khi thực hiện với chúng. Một chủ đề được cho là sở hữu khóa nội tại giữa thời gian nó có được khóa và phát hành khóa. Miễn là một luồng sở hữu một khóa nội tại, không có luồng nào khác có thể có được cùng một khóa. Các luồng khác sẽ chặn khi nó cố gắng để có được khóa.

package test;

public class SynchTest implements Runnable {  
    private int c = 0;

    public static void main(String[] args) {
        new SynchTest().test();
    }

    public void test() {
        // Create the object with the run() method
        Runnable runnable = new SynchTest();
        Runnable runnable2 = new SynchTest();
        // Create the thread supplying it with the runnable object
        Thread thread = new Thread(runnable,"thread-1");
        Thread thread2 = new Thread(runnable,"thread-2");
//      Here the key point is passing same object, if you pass runnable2 for thread2,
//      then its not applicable for synchronization test and that wont give expected
//      output Synchronization method means "it is not possible for two invocations
//      of synchronized methods on the same object to interleave"

        // Start the thread
        thread.start();
        thread2.start();
    }

    public synchronized  void increment() {
        System.out.println("Begin thread " + Thread.currentThread().getName());
        System.out.println(this.hashCode() + "Value of C = " + c);
//      If we uncomment this for synchronized block, then the result would be different
//      synchronized(this) {
            for (int i = 0; i < 9999999; i++) {
                c += i;
            }
//      }
        System.out.println("End thread " + Thread.currentThread().getName());
    }

//    public synchronized void decrement() {
//        System.out.println("Decrement " + Thread.currentThread().getName());
//    }

    public int value() {
        return c;
    }

    @Override
    public void run() {
        this.increment();
    }
}

Kiểm tra chéo các đầu ra khác nhau với phương thức đồng bộ, chặn và không đồng bộ hóa.


10
+1 vì là người duy nhất cho đến nay đề cập đến việc các nhà xây dựng không thể được đồng bộ hóa . Đó là, trong một hàm tạo, bạn thực sự chỉ có một tùy chọn: Các khối được đồng bộ hóa.
ef2011

Tôi đã kiểm tra mã của bạn theo chỉ dẫn nhưng C luôn là 0, sau đó -2024260031 và điều duy nhất thay đổi mã băm. Những hành vi nên được nhìn thấy?
Justin Johnson

Bạn nên đã trích dẫn bên dưới bài viết từ đó nội dung đã được quy định: docs.oracle.com/javase/tutorial/essential/concurrency/...docs.oracle.com/javase/tutorial/essential/concurrency/...
Ravindra babu

29

Lưu ý: các phương thức và khối được đồng bộ hóa tĩnh hoạt động trên đối tượng Class.

public class MyClass {
   // locks MyClass.class
   public static synchronized void foo() {
// do something
   }

   // similar
   public static void foo() {
      synchronized(MyClass.class) {
// do something
      }
   }
}

18

Khi trình biên dịch java chuyển đổi mã nguồn của bạn thành mã byte, nó xử lý các phương thức được đồng bộ hóa và các khối được đồng bộ hóa rất khác nhau.

Khi JVM thực thi một phương thức được đồng bộ hóa, luồng thực thi xác định rằng cấu trúc method_info của phương thức có cờ ACC_SYNCHRONIZED, sau đó nó tự động lấy khóa của đối tượng, gọi phương thức và giải phóng khóa. Nếu một ngoại lệ xảy ra, luồng sẽ tự động giải phóng khóa.

Mặt khác, đồng bộ hóa một khối phương thức, bỏ qua hỗ trợ tích hợp của JVM để có được khóa và xử lý ngoại lệ của đối tượng và yêu cầu chức năng phải được ghi rõ ràng bằng mã byte. Nếu bạn đọc mã byte cho một phương thức có khối được đồng bộ hóa, bạn sẽ thấy hơn một chục thao tác bổ sung để quản lý chức năng này.

Điều này hiển thị các lệnh gọi để tạo cả phương thức được đồng bộ hóa và khối được đồng bộ hóa:

public class SynchronizationExample {
    private int i;

    public synchronized int synchronizedMethodGet() {
        return i;
    }

    public int synchronizedBlockGet() {
        synchronized( this ) {
            return i;
        }
    }
}

Các synchronizedMethodGet()phương pháp tạo ra mã byte sau:

0:  aload_0
1:  getfield
2:  nop
3:  iconst_m1
4:  ireturn

Và đây là mã byte từ synchronizedBlockGet()phương thức:

0:  aload_0
1:  dup
2:  astore_1
3:  monitorenter
4:  aload_0
5:  getfield
6:  nop
7:  iconst_m1
8:  aload_1
9:  monitorexit
10: ireturn
11: astore_2
12: aload_1
13: monitorexit
14: aload_2
15: athrow

Một sự khác biệt đáng kể giữa phương thức và khối được đồng bộ hóa là, khối được đồng bộ hóa thường giảm phạm vi khóa. Vì phạm vi khóa tỷ lệ nghịch với hiệu suất, nên chỉ khóa phần quan trọng của mã. Một trong những ví dụ tốt nhất về việc sử dụng khối được đồng bộ hóa là khóa được kiểm tra hai lần trong mẫu Singleton trong đó thay vì khóa toàn bộ getInstance()phương thức, chúng tôi chỉ khóa phần mã quan trọng được sử dụng để tạo cá thể Singleton. Điều này cải thiện hiệu suất mạnh mẽ vì khóa chỉ được yêu cầu một hoặc hai lần.

Trong khi sử dụng các phương thức được đồng bộ hóa, bạn sẽ cần phải cẩn thận hơn nếu bạn trộn lẫn cả hai phương thức được đồng bộ hóa tĩnh và không tĩnh.


1
Nếu chúng ta nhìn vào phương thức được đồng bộ hóa bởi mã byte thì nhỏ gọn và đơn giản hơn, vậy tại sao khối đó không được đồng bộ hóa nhanh hơn?
eatS ngủCode

@eatS ngủCode Lưu ý rằng đây là mã byte, được JVM "biên dịch" thêm. JVM sẽ thêm vào cần thiết monitorentermonitorexittrước khi chạy mã.
Philip Couling

12

Thông thường tôi sử dụng điều này để đồng bộ hóa quyền truy cập vào danh sách hoặc bản đồ nhưng tôi không muốn chặn quyền truy cập vào tất cả các phương thức của đối tượng.

Trong đoạn mã sau, một luồng sửa đổi danh sách sẽ không chặn chờ một luồng đang sửa đổi bản đồ. Nếu các phương thức được đồng bộ hóa trên đối tượng thì mỗi phương thức sẽ phải chờ mặc dù các sửa đổi mà chúng đang thực hiện sẽ không xung đột.

private List<Foo> myList = new ArrayList<Foo>();
private Map<String,Bar) myMap = new HashMap<String,Bar>();

public void put( String s, Bar b ) {
  synchronized( myMap ) {
    myMap.put( s,b );
    // then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public void hasKey( String s, ) {
  synchronized( myMap ) {
    myMap.hasKey( s );
  }
}

public void add( Foo f ) {
  synchronized( myList ) {
    myList.add( f );
// then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public Thing getMedianFoo() {
  Foo med = null;
  synchronized( myList ) {
    Collections.sort(myList);
    med = myList.get(myList.size()/2); 
  }
  return med;
}

7

Với các khối được đồng bộ hóa, bạn có thể có nhiều bộ đồng bộ hóa, để nhiều thứ đồng thời nhưng không xung đột có thể diễn ra cùng một lúc.


6

Phương pháp đồng bộ hóa có thể được kiểm tra bằng API phản chiếu. Điều này có thể hữu ích để thử nghiệm một số hợp đồng, chẳng hạn như tất cả các phương thức trong mô hình được đồng bộ hóa .

Đoạn mã sau in tất cả các phương thức được đồng bộ hóa của Hashtable:

for (Method m : Hashtable.class.getMethods()) {
        if (Modifier.isSynchronized(m.getModifiers())) {
            System.out.println(m);
        }
}

5

Lưu ý quan trọng về việc sử dụng khối được đồng bộ hóa: cẩn thận những gì bạn sử dụng làm đối tượng khóa!

Đoạn mã từ user2277816 ở trên minh họa điểm này trong đó một tham chiếu đến một chuỗi ký tự được sử dụng làm đối tượng khóa. Nhận ra rằng chuỗi ký tự được tự động thực hiện trong Java và bạn sẽ bắt đầu thấy vấn đề: mọi đoạn mã được đồng bộ hóa trên "khóa" theo nghĩa đen, chia sẻ cùng một khóa! Điều này có thể dễ dàng dẫn đến bế tắc với các đoạn mã hoàn toàn không liên quan.

Nó không chỉ là các đối tượng String mà bạn cần phải cẩn thận. Các nguyên hàm đóng hộp cũng là một mối nguy hiểm, vì autoboxing và các phương thức valueOf có thể sử dụng lại các đối tượng tương tự, tùy thuộc vào giá trị.

Để biết thêm thông tin, hãy xem: https://www.securecoding.cert.org/confluence/display/java/LCK01-J.+Do+not+synyncize+on+objects+that+may+be+reuse


5

Thường sử dụng khóa trên cấp độ phương pháp là quá thô lỗ. Tại sao khóa một đoạn mã không truy cập bất kỳ tài nguyên được chia sẻ nào bằng cách khóa toàn bộ phương thức. Vì mỗi đối tượng có một khóa, bạn có thể tạo các đối tượng giả để thực hiện đồng bộ hóa cấp khối. Mức khối hiệu quả hơn vì nó không khóa toàn bộ phương thức.

Dưới đây là một số ví dụ

Cấp độ phương pháp

class MethodLevel {

  //shared among threads
SharedResource x, y ;

public void synchronized method1() {
   //multiple threads can't access
}
public void synchronized method2() {
  //multiple threads can't access
}

 public void method3() {
  //not synchronized
  //multiple threads can access
 }
}

Cấp khối

class BlockLevel {
  //shared among threads
  SharedResource x, y ;

  //dummy objects for locking
  Object xLock = new Object();
  Object yLock = new Object();

    public void method1() {
     synchronized(xLock){
    //access x here. thread safe
    }

    //do something here but don't use SharedResource x, y
    // because will not be thread-safe
     synchronized(xLock) {
       synchronized(yLock) {
      //access x,y here. thread safe
      }
     }

     //do something here but don't use SharedResource x, y
     //because will not be thread-safe
    }//end of method1
 }

[Biên tập]

Để Collectionthích VectorHashtablechúng được đồng bộ hóa khi ArrayListhoặc HashMapkhông và bạn cần đặt từ khóa được đồng bộ hóa hoặc gọi phương thức được đồng bộ hóa Bộ sưu tập:

Map myMap = Collections.synchronizedMap (myMap); // single lock for the entire map
List myList = Collections.synchronizedList (myList); // single lock for the entire list

5

Sự khác biệt duy nhất: các khối được đồng bộ hóa cho phép khóa dạng hạt không giống như phương thức được đồng bộ hóa

Về cơ bản synchronizedkhối hoặc phương thức đã được sử dụng để viết mã an toàn của luồng bằng cách tránh các lỗi không nhất quán bộ nhớ.

Câu hỏi này rất cũ và nhiều thứ đã được thay đổi trong suốt 7 năm qua. Các cấu trúc lập trình mới đã được giới thiệu để đảm bảo an toàn cho luồng.

Bạn có thể đạt được sự an toàn của luồng bằng cách sử dụng API đồng thời nâng cao thay vì các synchroniedkhối. Trang tài liệu này cung cấp các cấu trúc lập trình tốt để đạt được sự an toàn của luồng.

Khóa Đối tượng hỗ trợ khóa thành ngữ đơn giản hóa nhiều ứng dụng đồng thời.

Các nhà điều hành xác định API cấp cao để khởi chạy và quản lý các luồng. Việc triển khai thực thi được cung cấp bởi java.util.conc hiện cung cấp quản lý nhóm luồng phù hợp cho các ứng dụng quy mô lớn.

Bộ sưu tập đồng thời giúp quản lý bộ sưu tập dữ liệu lớn dễ dàng hơn và có thể giảm đáng kể nhu cầu đồng bộ hóa.

Biến nguyên tử có các tính năng giảm thiểu đồng bộ hóa và giúp tránh các lỗi thống nhất bộ nhớ.

ThreadLocalRandom (trong JDK 7) cung cấp việc tạo số giả ngẫu nhiên hiệu quả từ nhiều luồng.

Thay thế tốt hơn cho đồng bộ hóa là ReentrantLock , sử dụng LockAPI

Khóa loại trừ lẫn nhau reentrant với cùng một hành vi và ngữ nghĩa cơ bản giống như khóa màn hình ngầm được truy cập bằng các phương thức và câu lệnh được đồng bộ hóa, nhưng với khả năng mở rộng.

Ví dụ với khóa:

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

Tham khảo java.util.concurrentjava.util.concurrent.atomic gói quá cho các cấu trúc lập trình khác.

Tham khảo câu hỏi liên quan này quá:

Đồng bộ hóa với Khóa


4

Phương pháp đồng bộ hóa được sử dụng để khóa tất cả các đối tượng Khối đồng bộ hóa được sử dụng để khóa đối tượng cụ thể


3

Nói chung, những thứ này hầu hết đều giống nhau ngoài việc rõ ràng về màn hình của đối tượng đang được sử dụng so với đối tượng ẩn này. Một nhược điểm của các phương thức được đồng bộ hóa mà tôi nghĩ đôi khi bị bỏ qua là trong việc sử dụng tham chiếu "này" để đồng bộ hóa trên bạn sẽ bỏ ngỏ khả năng các đối tượng bên ngoài khóa trên cùng một đối tượng. Đó có thể là một lỗi rất tinh tế nếu bạn gặp phải nó. Đồng bộ hóa trên Đối tượng rõ ràng nội bộ hoặc trường hiện có khác có thể tránh được sự cố này, hoàn toàn đóng gói đồng bộ hóa.


2

Như đã nói ở đây, khối được đồng bộ hóa có thể sử dụng biến do người dùng xác định làm đối tượng khóa, khi chức năng được đồng bộ hóa chỉ sử dụng "này". Và tất nhiên bạn có thể thao tác với các khu vực chức năng của bạn sẽ được đồng bộ hóa. Nhưng mọi người đều nói rằng không có sự khác biệt giữa chức năng được đồng bộ hóa và khối bao gồm toàn bộ chức năng sử dụng "this" làm đối tượng khóa. Điều đó không đúng, sự khác biệt nằm ở mã byte sẽ được tạo ra trong cả hai tình huống. Trong trường hợp sử dụng khối đồng bộ nên được phân bổ biến cục bộ chứa tham chiếu đến "này". Và kết quả là chúng ta sẽ có kích thước lớn hơn một chút cho chức năng (không liên quan nếu bạn chỉ có một số ít chức năng).

Giải thích chi tiết hơn về sự khác biệt bạn có thể tìm thấy ở đây: http://www.artima.com/insidejvm/ed2/threadsynchP.html


2

Trong trường hợp phương thức được đồng bộ hóa, khóa sẽ được lấy trên Object. Nhưng nếu bạn đi với khối được đồng bộ hóa, bạn có một tùy chọn để chỉ định một đối tượng mà khóa sẽ được lấy.

Thí dụ :

    Class Example {
    String test = "abc";
    // lock will be acquired on String  test object.
    synchronized (test) {
        // do something
    }

   lock will be acquired on Example Object
   public synchronized void testMethod() {
     // do some thing
   } 

   }

2

Tôi biết đây là một câu hỏi cũ, nhưng với việc đọc nhanh các câu trả lời ở đây, tôi thực sự không thấy ai đề cập rằng đôi khi một synchronizedphương pháp có thể là khóa sai .
Từ thực hành đồng thời Java (trang 72):

public class ListHelper<E> {
  public List<E> list = Collections.syncrhonizedList(new ArrayList<>());
...

public syncrhonized boolean putIfAbsent(E x) {
 boolean absent = !list.contains(x);
if(absent) {
 list.add(x);
}
return absent;
}

Các mã trên có sự xuất hiện của an toàn chủ đề. Tuy nhiên, trong thực tế thì không. Trong trường hợp này, khóa được lấy theo thể hiện của lớp. Tuy nhiên, danh sách có thể được sửa đổi bởi một luồng khác không sử dụng phương thức đó. Cách tiếp cận đúng sẽ là sử dụng

public boolean putIfAbsent(E x) {
 synchronized(list) {
  boolean absent = !list.contains(x);
  if(absent) {
    list.add(x);
  }
  return absent;
}
}

Đoạn mã trên sẽ chặn tất cả các luồng cố gắng sửa đổi danh sách từ sửa đổi danh sách cho đến khi khối được đồng bộ hóa hoàn tất.


đọc cuốn sách này vào lúc này ... tôi tự hỏi ... nếu danh sách đó là riêng tư thay vì công khai và chỉ có phương thức put IfAbsent, được đồng bộ hóa (điều này) sẽ là đủ phải không? Vấn đề trong tay là vì danh sách có thể được sửa đổi bên ngoài ListHelper không?
dtc

@dtc yea nếu danh sách là riêng tư và không bị rò rỉ ở bất kỳ nơi nào khác trong lớp thì điều đó là đủ, miễn là bạn đánh dấu mọi phương thức khác trong lớp để sửa đổi danh sách cũng được đồng bộ hóa. Tuy nhiên, việc khóa toàn bộ phương thức thay vì chỉ Listcó thể dẫn đến các vấn đề về hiệu năng nếu có nhật ký mã không nhất thiết phải được đồng bộ hóa
aarbor

Điều đó có ý nghĩa. cảm ơn rất nhiều vì đã trả lời tbh, tôi đã tìm thấy cuốn sách khá hữu ích trong việc mở rộng kiến ​​thức và cách tiếp cận đa luồng nhưng nó cũng giới thiệu một thế giới hoàn toàn mới với tôi
dtc

2

Là một vấn đề thực tế, lợi thế của các phương thức được đồng bộ hóa so với các khối được đồng bộ hóa là chúng có khả năng chống ngốc hơn; bởi vì bạn không thể chọn một đối tượng tùy ý để khóa, bạn không thể sử dụng sai cú pháp phương thức được đồng bộ hóa để thực hiện những điều ngu ngốc như khóa chuỗi ký tự hoặc khóa nội dung của trường có thể thay đổi được thay đổi từ bên dưới các luồng.

Mặt khác, với các phương thức được đồng bộ hóa, bạn không thể bảo vệ khóa khỏi bị thu thập bởi bất kỳ luồng nào có thể có được tham chiếu đến đối tượng.

Vì vậy, sử dụng đồng bộ hóa như một công cụ sửa đổi trên các phương pháp sẽ tốt hơn trong việc bảo vệ những người chăn bò của bạn khỏi bị tổn thương, trong khi sử dụng các khối được đồng bộ hóa kết hợp với các đối tượng khóa riêng cuối cùng sẽ tốt hơn trong việc bảo vệ mã của bạn khỏi các orker bò.


1

Từ một bản tóm tắt đặc tả Java: http://www.cs.cornell.edu/andru/javaspec/17.doc.html

Câu lệnh được đồng bộ hóa (§14,17) tính toán một tham chiếu đến một đối tượng; sau đó nó cố gắng thực hiện một hành động khóa trên đối tượng đó và không tiến hành thêm cho đến khi hành động khóa đã hoàn thành thành công. ...

Phương thức được đồng bộ hóa (§8.4.3.5) tự động thực hiện hành động khóa khi được gọi; cơ thể của nó không được thực thi cho đến khi hành động khóa đã hoàn thành. Nếu phương thức là một phương thức cá thể , nó sẽ khóa khóa được liên kết với thể hiện mà nó được gọi (nghĩa là, đối tượng sẽ được gọi là phương thức này trong khi thực thi phần thân của phương thức). Nếu phương thức là tĩnh , nó sẽ khóa khóa được liên kết với đối tượng Class đại diện cho lớp mà phương thức được định nghĩa. ...

Dựa trên những mô tả này, tôi sẽ nói hầu hết các câu trả lời trước đều đúng và một phương thức được đồng bộ hóa có thể đặc biệt hữu ích cho các phương thức tĩnh, trong đó bạn sẽ phải tìm ra cách lấy "Đối tượng lớp đại diện cho lớp trong đó phương thức đó là được xác định. "

Chỉnh sửa: Ban đầu tôi nghĩ đây là những trích dẫn của thông số Java thực tế. Làm rõ rằng trang này chỉ là một bản tóm tắt / giải thích về thông số kỹ thuật


1

TLDR; Không sử dụng công cụ synchronizedsửa đổi cũng như synchronized(this){...}biểu thức synchronized(myLock){...}myLocklà trường đối tượng cuối cùng chứa một đối tượng riêng.


Sự khác biệt giữa việc sử dụng công cụ synchronizedsửa đổi trên khai báo phương thức và synchronized(..){ }biểu thức trong thân phương thức là:

  • Công synchronizedcụ sửa đổi được chỉ định trên chữ ký của phương thức
    1. hiển thị trong JavaDoc được tạo,
    2. có thể xác định được bằng lập trình thông qua sự phản chiếu khi kiểm tra công cụ sửa đổi của phương thức cho Công cụ sửa đổi.SYNCHRONIZED ,
    3. yêu cầu ít gõ và chú ý hơn so với synchronized(this) { .... }, và
    4. (tùy thuộc vào IDE của bạn) hiển thị trong phác thảo lớp và hoàn thành mã,
    5. sử dụng thisđối tượng là khóa khi khai báo trên phương thức không tĩnh hoặc lớp kèm theo khi khai báo trên phương thức tĩnh.
  • Các synchronized(...){...}biểu hiện cho phép bạn
    1. chỉ đồng bộ hóa việc thực hiện các phần của cơ thể phương thức,
    2. được sử dụng trong hàm tạo hoặc khối khởi tạo ( tĩnh ),
    3. để chọn đối tượng khóa điều khiển truy cập được đồng bộ hóa.

Tuy nhiên, sử dụng công cụ synchronizedsửa đổi hoặc synchronized(...) {...}với thisđối tượng khóa (như trong synchronized(this) {...}), có cùng nhược điểm. Cả hai đều sử dụng thể hiện riêng của nó làm đối tượng khóa để đồng bộ hóa. Điều này rất nguy hiểm vì không chỉ bản thân đối tượng mà bất kỳ đối tượng / mã bên ngoài nào có tham chiếu đến đối tượng đó cũng có thể sử dụng nó làm khóa đồng bộ hóa với các tác dụng phụ nghiêm trọng tiềm ẩn (suy giảm hiệu suất và bế tắc ).

Do đó, cách tốt nhất là không sử dụng công cụ synchronizedsửa đổi cũng như synchronized(...)biểu thức kết hợp với thisđối tượng khóa mà là đối tượng khóa riêng tư đối tượng này. Ví dụ:

public class MyService {
    private final lock = new Object();

    public void doThis() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }

    public void doThat() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }
}

Bạn cũng có thể sử dụng nhiều đối tượng khóa nhưng cần đặc biệt cẩn thận để đảm bảo điều này không dẫn đến bế tắc khi sử dụng lồng nhau.

public class MyService {
    private final lock1 = new Object();
    private final lock2 = new Object();

    public void doThis() {
       synchronized(lock1) {
          synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThat() and doMore().
          }
    }

    public void doThat() {
       synchronized(lock1) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doMore() may execute concurrently
        }
    }

    public void doMore() {
       synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doThat() may execute concurrently
        }
    }
}

1

Tôi cho rằng câu hỏi này là về sự khác biệt giữa Thread Safe SingletonLazy khởi tạo với khóa kiểm tra Double . Tôi luôn đề cập đến bài viết này khi tôi cần thực hiện một số singleton cụ thể.

Chà, đây là một chủ đề an toàn Singleton :

// Java program to create Thread Safe 
// Singleton class 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

 //synchronized method to control simultaneous access 
  synchronized public static GFG getInstance()  
  { 
    if (instance == null)  
    { 
      // if instance is null, initialize 
      instance = new GFG(); 
    } 
    return instance; 
  } 
} 

Ưu điểm:

  1. Lười khởi tạo là có thể.

  2. Đây là chủ đề an toàn.

Nhược điểm:

  1. Phương thức getInstance () được đồng bộ hóa nên gây ra hiệu năng chậm vì nhiều luồng không thể truy cập đồng thời.

Đây là một khởi tạo lười biếng với khóa kiểm tra kép :

// Java code to explain double check locking 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

  public static GFG getInstance() 
  { 
    if (instance == null)  
    { 
      //synchronized block to remove overhead 
      synchronized (GFG.class) 
      { 
        if(instance==null) 
        { 
          // if instance is null, initialize 
          instance = new GFG(); 
        } 

      } 
    } 
    return instance; 
  } 
} 

Ưu điểm:

  1. Lười khởi tạo là có thể.

  2. Nó cũng là chủ đề an toàn.

  3. Hiệu suất giảm vì từ khóa đồng bộ được khắc phục.

Nhược điểm:

  1. Lần đầu tiên, nó có thể ảnh hưởng đến hiệu suất.

  2. Như khuyết điểm. phương pháp khóa kiểm tra kép có thể chịu được để có thể sử dụng cho các ứng dụng đa luồng hiệu suất cao.

Vui lòng tham khảo bài viết này để biết thêm chi tiết:

https://www.geekforgeek.org/java-singleton-design-potype-practices-examples/


-3

Đồng bộ hóa với các chủ đề. 1) KHÔNG BAO GIỜ sử dụng đồng bộ hóa (cái này) trong một chủ đề mà nó không hoạt động. Đồng bộ hóa với (cái này) sử dụng luồng hiện tại làm đối tượng luồng khóa. Vì mỗi luồng độc lập với các luồng khác, nên KHÔNG có sự phối hợp đồng bộ hóa. 2) Các thử nghiệm mã cho thấy rằng trong Java 1.6 trên máy Mac, đồng bộ hóa phương thức không hoạt động. 3) được đồng bộ hóa (lockObj) trong đó lockObj là một đối tượng chia sẻ chung của tất cả các luồng đồng bộ hóa trên nó sẽ hoạt động. 4) ReenterantLock.lock () và .unlock () hoạt động. Xem hướng dẫn Java cho điều này.

Các mã sau đây cho thấy những điểm này. Nó cũng chứa Vector an toàn luồng sẽ được thay thế cho ArrayList, để cho thấy rằng nhiều luồng thêm vào Vector không bị mất bất kỳ thông tin nào, trong khi cùng với ArrayList có thể mất thông tin. 0) Mã hiện tại cho thấy mất thông tin do điều kiện chủng tộc A) Nhận xét dòng A được gắn nhãn hiện tại và bỏ ghi chú dòng A phía trên nó, sau đó chạy, phương thức mất dữ liệu nhưng không nên. B) Đảo ngược bước A, bỏ ghi chú B và // khối kết thúc}. Sau đó chạy để xem kết quả không mất dữ liệu C) Nhận xét B, không ghi chú C. Chạy, xem đồng bộ hóa trên (điều này) mất dữ liệu, như mong đợi. Không có thời gian để hoàn thành tất cả các biến thể, hy vọng điều này sẽ giúp. Nếu đồng bộ hóa trên (cái này) hoặc đồng bộ hóa phương thức hoạt động, vui lòng cho biết phiên bản Java và HĐH nào bạn đã thử nghiệm. Cảm ơn bạn.

import java.util.*;

/** RaceCondition - Shows that when multiple threads compete for resources 
     thread one may grab the resource expecting to update a particular 
     area but is removed from the CPU before finishing.  Thread one still 
     points to that resource.  Then thread two grabs that resource and 
     completes the update.  Then thread one gets to complete the update, 
     which over writes thread two's work.
     DEMO:  1) Run as is - see missing counts from race condition, Run severa times, values change  
            2) Uncomment "synchronized(countLock){ }" - see counts work
            Synchronized creates a lock on that block of code, no other threads can 
            execute code within a block that another thread has a lock.
        3) Comment ArrayList, unComment Vector - See no loss in collection
            Vectors work like ArrayList, but Vectors are "Thread Safe"
         May use this code as long as attribution to the author remains intact.
     /mf
*/ 

public class RaceCondition {
    private ArrayList<Integer> raceList = new ArrayList<Integer>(); // simple add(#)
//  private Vector<Integer> raceList = new Vector<Integer>(); // simple add(#)

    private String countLock="lock";    // Object use for locking the raceCount
    private int raceCount = 0;        // simple add 1 to this counter
    private int MAX = 10000;        // Do this 10,000 times
    private int NUM_THREADS = 100;    // Create 100 threads

    public static void main(String [] args) {
    new RaceCondition();
    }

    public RaceCondition() {
    ArrayList<Thread> arT = new ArrayList<Thread>();

    // Create thread objects, add them to an array list
    for( int i=0; i<NUM_THREADS; i++){
        Thread rt = new RaceThread( ); // i );
        arT.add( rt );
    }

    // Start all object at once.
    for( Thread rt : arT ){
        rt.start();
    }

    // Wait for all threads to finish before we can print totals created by threads
    for( int i=0; i<NUM_THREADS; i++){
        try { arT.get(i).join(); }
        catch( InterruptedException ie ) { System.out.println("Interrupted thread "+i); }
    }

    // All threads finished, print the summary information.
    // (Try to print this informaiton without the join loop above)
    System.out.printf("\nRace condition, should have %,d. Really have %,d in array, and count of %,d.\n",
                MAX*NUM_THREADS, raceList.size(), raceCount );
    System.out.printf("Array lost %,d. Count lost %,d\n",
             MAX*NUM_THREADS-raceList.size(), MAX*NUM_THREADS-raceCount );
    }   // end RaceCondition constructor



    class RaceThread extends Thread {
    public void run() {
        for ( int i=0; i<MAX; i++){
        try {
            update( i );        
        }    // These  catches show when one thread steps on another's values
        catch( ArrayIndexOutOfBoundsException ai ){ System.out.print("A"); }
        catch( OutOfMemoryError oome ) { System.out.print("O"); }
        }
    }

    // so we don't lose counts, need to synchronize on some object, not primitive
    // Created "countLock" to show how this can work.
    // Comment out the synchronized and ending {, see that we lose counts.

//    public synchronized void update(int i){   // use A
    public void update(int i){                  // remove this when adding A
//      synchronized(countLock){            // or B
//      synchronized(this){             // or C
        raceCount = raceCount + 1;
        raceList.add( i );      // use Vector  
//          }           // end block for B or C
    }   // end update

    }   // end RaceThread inner class


} // end RaceCondition outter class

1
Đồng bộ hóa với '(this)' không làm việc, và không không 'sử dụng thread hiện hành như đối tượng đồng bộ hóa, trừ các đối tượng hiện nay là của một lớp mà kéo dài Thread. -1
Hầu tước Lorne
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.