JavaRush /Blog Java /Random-VI /Đồng bộ hóa chủ đề. Toán tử được đồng bộ hóa trong Java

Đồng bộ hóa chủ đề. Toán tử được đồng bộ hóa trong Java

Xuất bản trong nhóm
Xin chào! Hôm nay chúng ta sẽ tiếp tục xem xét các tính năng của lập trình đa luồng và nói về đồng bộ hóa luồng.
Đồng bộ hóa chủ đề.  Toán tử đồng bộ - 1
"đồng bộ hóa" là gì? Bên ngoài lĩnh vực lập trình, điều này đề cập đến một số loại thiết lập cho phép hai thiết bị hoặc chương trình hoạt động cùng nhau. Ví dụ: điện thoại thông minh và máy tính có thể được đồng bộ hóa với tài khoản Google và tài khoản cá nhân trên trang web có thể được đồng bộ hóa với các tài khoản trên mạng xã hội để đăng nhập bằng chúng. Đồng bộ hóa luồng có ý nghĩa tương tự: nó thiết lập cách các luồng tương tác với nhau. Trong các bài giảng trước, các chủ đề của chúng tôi tồn tại và hoạt động riêng biệt với nhau. Một người đang đếm thứ gì đó, người thứ hai đang ngủ, người thứ ba đang hiển thị thứ gì đó trên bảng điều khiển, nhưng họ không tương tác với nhau. Trong các chương trình thực tế, những tình huống như vậy rất hiếm. Ví dụ: một số luồng có thể hoạt động tích cực với cùng một bộ dữ liệu và thay đổi nội dung nào đó trong đó. Điều này tạo ra vấn đề. Hãy tưởng tượng rằng nhiều luồng đang ghi văn bản vào cùng một vị trí, chẳng hạn như tệp văn bản hoặc bảng điều khiển. Tệp hoặc bảng điều khiển này trong trường hợp này sẽ trở thành tài nguyên được chia sẻ. Các luồng không biết về sự tồn tại của nhau nên chúng chỉ cần viết ra mọi thứ chúng có thể quản lý trong thời gian mà bộ lập lịch luồng phân bổ cho chúng. Trong một bài giảng gần đây của khóa học, chúng ta đã có một ví dụ về điều này sẽ dẫn đến điều gì, hãy nhớ điều đó: Đồng bộ hóa chủ đề.  Toán tử đồng bộ - 2Lý do nằm ở chỗ các luồng hoạt động với một tài nguyên dùng chung, bảng điều khiển, mà không phối hợp các hành động với nhau. Nếu bộ lập lịch luồng đã phân bổ thời gian cho Thread-1, nó sẽ ghi ngay mọi thứ vào bảng điều khiển. Những chủ đề khác đã viết được hoặc chưa viết được đều không quan trọng. Kết quả, như bạn có thể thấy, là một thảm họa. Do đó, trong lập trình đa luồng, một khái niệm đặc biệt mutex đã được đưa ra (từ tiếng Anh “mutex”, “loại trừ lẫn nhau” - “loại trừ lẫn nhau”) . Mục đích của mutex là cung cấp cơ chế sao cho chỉ một luồng có quyền truy cập vào một đối tượng tại một thời điểm nhất định. Nếu Thread-1 đã có được mutex của đối tượng A, các luồng khác sẽ không có quyền truy cập vào nó để thay đổi bất cứ điều gì trong đó. Cho đến khi mutex của đối tượng A được giải phóng, các thread còn lại sẽ buộc phải chờ. Ví dụ thực tế: hãy tưởng tượng rằng bạn và 10 người lạ khác đang tham gia một khóa đào tạo. Bạn cần thay phiên nhau bày tỏ ý tưởng và thảo luận về điều gì đó. Tuy nhiên, vì đây là lần đầu tiên các bạn gặp nhau, để không liên tục ngắt lời nhau và không rơi vào tình trạng huyên náo, bạn hãy sử dụng quy tắc “bóng nói”: chỉ một người có thể nói - người có bóng trong đó. tay của anh ấy. Bằng cách này, cuộc thảo luận trở nên đầy đủ và hiệu quả. Vì vậy, về bản chất, mutex là một quả bóng như vậy. Nếu mutex của một đối tượng nằm trong tay một luồng thì các luồng khác sẽ không thể truy cập vào đối tượng đó. Bạn không cần phải làm bất cứ điều gì để tạo một mutex: nó đã được tích hợp sẵn trong lớp Object, có nghĩa là mọi đối tượng trong Java đều có nó.

Cách toán tử được đồng bộ hóa hoạt động trong Java

Hãy làm quen với từ khóa mới - được đồng bộ hóa . Nó đánh dấu một đoạn mã nhất định của chúng tôi. Nếu một khối mã được đánh dấu bằng từ khóa được đồng bộ hóa, điều đó có nghĩa là khối đó chỉ có thể được thực thi bởi một luồng tại một thời điểm. Đồng bộ hóa có thể được thực hiện theo nhiều cách khác nhau. Ví dụ: tạo toàn bộ phương thức được đồng bộ hóa:
public synchronized void doSomething() {

   //...method logic
}
Hoặc viết một khối mã nơi việc đồng bộ hóa được thực hiện trên một số đối tượng:
public class Main {

   private Object obj = new Object();

   public void doSomething() {

       //...some logic available to all threads

       synchronized (obj) {

           //logic that is only available to one thread at a time
       }
   }
}
Ý nghĩa rất đơn giản. Nếu một luồng đi vào một khối mã được đánh dấu bằng từ được đồng bộ hóa, thì nó sẽ ngay lập tức nhận được mutex của đối tượng và tất cả các luồng khác cố gắng nhập cùng một khối hoặc phương thức đó buộc phải đợi cho đến khi luồng trước đó hoàn thành công việc của nó và giải phóng khối mã đó. màn hình. Đồng bộ hóa chủ đề.  Toán tử đồng bộ - 3Nhân tiện! Trong các bài giảng của khóa học, bạn đã thấy các ví dụ về đồng bộ hóa, nhưng chúng trông khác:
public void swap()
{
   synchronized (this)
   {
       //...method logic
   }
}
Chủ đề này còn mới đối với bạn và tất nhiên ban đầu sẽ có sự nhầm lẫn về cú pháp. Vì vậy, hãy ghi nhớ ngay để sau này không bị nhầm lẫn trong cách viết. Hai phương pháp viết này có ý nghĩa giống nhau:
public void swap() {

   synchronized (this)
   {
       //...method logic
   }
}


public synchronized void swap() {

   }
}
Trong trường hợp đầu tiên, bạn tạo một khối mã được đồng bộ hóa ngay khi nhập phương thức. Nó được đồng bộ hóa bởi đối tượng this, nghĩa là bởi đối tượng hiện tại. Và trong ví dụ thứ hai, bạn đặt từ được đồng bộ hóa trên toàn bộ phương thức. Không còn cần phải chỉ ra rõ ràng bất kỳ đối tượng nào được thực hiện đồng bộ hóa. Khi toàn bộ phương thức được đánh dấu bằng một từ, phương thức này sẽ tự động được đồng bộ hóa cho tất cả các đối tượng của lớp. Chúng ta đừng đi sâu vào cuộc thảo luận về phương pháp nào tốt hơn. Bây giờ, hãy chọn những gì bạn thích nhất :) Điều chính cần nhớ: bạn chỉ có thể khai báo một phương thức được đồng bộ hóa khi tất cả logic bên trong nó được thực thi bởi một luồng cùng một lúc. Ví dụ: trong trường hợp này doSomething()sẽ xảy ra lỗi khi đồng bộ hóa phương thức:
public class Main {

   private Object obj = new Object();

   public void doSomething() {

       //...some logic available to all threads

       synchronized (obj) {

           //logic that is only available to one thread at a time
       }
   }
}
Như bạn có thể thấy, một phần của phương thức này chứa logic không cần phải đồng bộ hóa. Mã trong đó có thể được thực thi đồng thời bởi nhiều luồng và tất cả các vị trí quan trọng được phân bổ cho một khối được đồng bộ hóa riêng biệt. Và một khoảnh khắc. Chúng ta hãy nhìn dưới kính hiển vi vào ví dụ của chúng tôi trong bài giảng về việc đổi tên:
public void swap()
{
   synchronized (this)
   {
       //...method logic
   }
}
Xin lưu ý: việc đồng bộ hóa được thực hiện bằng cách sử dụng this. Tức là đối với một đối tượng cụ thể MyClass. Hãy tưởng tượng rằng chúng ta có 2 luồng ( Thread-1Thread-2) và chỉ có một đối tượng MyClass myClass. Trong trường hợp này, nếu Thread-1phương thức được gọi myClass.swap(), mutex của đối tượng sẽ bận và Thread-2khi bạn cố gọi nó, myClass.swap()nó sẽ bị treo chờ mutex rảnh rỗi. Nếu chúng ta có 2 luồng và 2 đối tượng MyClass- myClass1myClass2- trên các đối tượng khác nhau, các luồng của chúng ta có thể dễ dàng thực thi đồng thời các phương thức được đồng bộ hóa. Chủ đề đầu tiên thực hiện:
myClass1.swap();
Cái thứ hai làm:
myClass2.swap();
Trong trường hợp này, từ khóa được đồng bộ hóa bên trong phương thức swap()sẽ không ảnh hưởng đến hoạt động của chương trình vì quá trình đồng bộ hóa được thực hiện trên một đối tượng cụ thể. Và trong trường hợp sau, chúng ta có 2 đối tượng, do đó, các luồng không tạo ra vấn đề cho nhau. Xét cho cùng, hai đối tượng có 2 mutex khác nhau và việc bắt chúng không phụ thuộc vào nhau.

Đặc điểm đồng bộ hóa trong phương thức tĩnh

Nhưng nếu bạn cần đồng bộ hóa một phương thức tĩnh thì sao?
class MyClass {
   private static String name1 = "Olya";
   private static String name2 = "Lena";

   public static synchronized void swap() {
       String s = name1;
       name1 = name2;
       name2 = s;
   }

}
Không rõ điều gì sẽ đóng vai trò là mutex trong trường hợp này. Rốt cuộc, chúng tôi đã quyết định rằng mọi đối tượng đều có một mutex. Nhưng vấn đề là để gọi một phương thức tĩnh MyClass.swap()chúng ta không cần các đối tượng: phương thức này là tĩnh! Vậy tiếp theo là gì? :/ Thực ra thì không có vấn đề gì với việc này cả. Những người tạo ra Java đã lo mọi việc :) Nếu phương thức chứa logic “đa luồng” quan trọng là tĩnh, thì việc đồng bộ hóa sẽ được thực hiện theo lớp. Để rõ ràng hơn, đoạn mã trên có thể được viết lại thành:
class MyClass {
   private static String name1 = "Olya";
   private static String name2 = "Lena";

   public static void swap() {

       synchronized (MyClass.class) {
           String s = name1;
           name1 = name2;
           name2 = s;
       }
   }

}
Về nguyên tắc, bạn có thể tự mình nghĩ ra điều này: vì không có đối tượng nên cơ chế đồng bộ hóa bằng cách nào đó phải được “kết nối cứng” vào chính các lớp. Đó là cách thực hiện: bạn cũng có thể đồng bộ hóa giữa các lớp.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION