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 trong Java là gì?

Bên ngoài miền lập trình, nó ngụ ý một sự sắp xế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 trang web có thể được đồng bộ hóa với tài khoản mạng xã hội để bạn có thể sử dụng chúng để đăng nhập. Đồng bộ hóa luồng có ý nghĩa tương tự: đó là sự sắp xếp trong đó các luồng tương tác với nhau. Trong các bài học trước, các luồng của chúng ta sống và hoạt động tách biệt với nhau. Một người thực hiện phép tính, người thứ hai ngủ và người thứ ba hiển thị thứ gì đó trên bảng điều khiển, nhưng họ không tương tác. Trong các chương trình thực tế, những tình huống như vậy rất hiếm. Nhiều luồng có thể tích cực làm việc và sửa đổi cùng một tập dữ liệu. Điều này tạo ra vấn đề. Hãy tưởng tượng nhiều luồng 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. Trong trường hợp này, tệp hoặc bảng điều khiển 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, vì vậy chúng chỉ cần viết mọi thứ có thể trong thời gian được bộ lập lịch trình luồng phân bổ cho chúng. Trong một bài học gần đây, chúng ta đã thấy một ví dụ về việc điều này sẽ dẫn đến đâu. Hãy nhớ lại nó ngay bây giờ: Đồng bộ hóa chủ đề.  Toán tử đồng bộ - 2Lý do nằm ở chỗ các luồng đang làm việc với một tài nguyên được chia sẻ (bảng điều khiển) mà không phối hợp các hành động của chúng với nhau. Nếu bộ lập lịch luồng phân bổ thời gian cho Chủ đề-1, thì nó sẽ ngay lập tức ghi mọi thứ vào bảng điều khiển. Những chủ đề khác có hoặc chưa quản lý để viết không quan trọng. Kết quả, như bạn có thể thấy, thật đáng thất vọng. Đó là lý do tại sao họ giới thiệu một khái niệm đặc biệt, mutex (loại trừ lẫn nhau) , cho lập trình đa luồng. Mục đích của một mutexlà cung cấp một cơ chế để 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 Chủ đề-1 có được mutex của đối tượng A, các chủ đề khác sẽ không thể truy cập và sửa đổi đối tượng. Các chủ đề khác phải đợi cho đến khi mutex của đối tượng A được giải phóng. Đây là một ví dụ từ cuộc sống: hãy tưởng tượng rằng bạn và 10 người lạ khác đang tham gia một bài tập. Thay phiên nhau, bạn cần bày tỏ ý tưởng của mình và thảo luận điều gì đó. Nhưng vì mới gặp nhau lần đầu nên để không liên tục ngắt lời nhau và nổi cơn thịnh nộ, bạn sử dụng 'quả bóng biết nói': chỉ người có quả bóng mới có thể nói được. Bằng cách này, bạn sẽ có một cuộc thảo luận tốt và hiệu quả. Về cơ bản, quả bóng là một mutex. Nếu một mutex của một đối tượng nằm trong tay của một luồng, các luồng khác không thể hoạt động với đối tượng.Objectlớp, có nghĩa là mọi đối tượng trong Java đều có một lớp.

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

Hãy làm quen với một từ khóa mới: được đồng bộ hóa . Nó được dùng để đánh dấu một khối mã nào đó. Nếu một khối mã được đánh dấu bằng synchronizedtừ khóa, thì 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ụ: bằng cách khai bá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 đồng bộ hóa được thực hiện bằng một số đối tượng:

public class Main {

   private Object obj = new Object();

   public void doSomething() {

       // ...Some logic available simultaneously to all threads

       synchronized (obj) {

           // Logic available to just one thread at a time
       }
   }
}
Ý nghĩa rất đơn giản. Nếu một luồng đi vào bên trong khối mã được đánh dấu bằng từ synchronizedkhóa, nó sẽ ngay lập tức nắm bắt được mutex của đối tượng và tất cả các luồng khác đang 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 màn hình. Đồng bộ hóa chủ đề.  Toán tử đồng bộ - 3Nhân tiện! Trong suốt khóa học, bạn đã thấy các ví dụ về synchronized, nhưng chúng có vẻ khác:

public void swap()
{
   synchronized (this)
   {
       // ...Method logic
   }
}
Chủ đề này là mới cho bạn. Và, tất nhiên, sẽ có sự nhầm lẫn với cú pháp. Vì vậy, hãy ghi nhớ nó ngay lập tức để tránh bị nhầm lẫn bởi các cách viết khác nhau. Hai cách 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 đang 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 thisđối tượng, tức là đối tượng hiện tại. Và trong ví dụ thứ hai, bạn áp dụng synchronizedtừ khóa cho toàn bộ phương thức. Điều này làm cho không cần thiết phải chỉ ra rõ ràng đối tượng đang được sử dụng để đồng bộ hóa. Vì toàn bộ phương thức được đánh dấu bằng từ khóa nên phương thức sẽ tự động được đồng bộ hóa cho tất cả các thể hiện của lớp. Chúng tôi sẽ không đi sâu vào một cuộc thảo luận về cách nào tốt hơn. Hiện tại, hãy chọn bất kỳ cách nào 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 của nó được thực thi bởi một luồng tại một thời điểm. Ví dụ: sẽ là sai lầm khi thực hiện doSomething()đồng bộ hóa phương thức sau:

public class Main {

   private Object obj = new Object();

   public void doSomething() {

       // ...Some logic available simultaneously to all threads

       synchronized (obj) {

           // Logic available to just one thread at a time
       }
   }
}
Như bạn có thể thấy, một phần của phương thức chứa logic không yêu cầu đồng bộ hóa. Mã đó có thể được chạy bởi nhiều luồng cùng một lúc và tất cả các vị trí quan trọng được đặt riêng biệt trong một synchronizedkhối riêng biệt. Và một điều nữa. Hãy xem xét kỹ ví dụ của chúng ta từ bài học đổi tên:

public void swap()
{
   synchronized (this)
   {
       // ...Method logic
   }
}
Lưu ý: đồng bộ hóa được thực hiện bằng cách sử dụngthis. Đó là, sử dụng mộtMyClassđối tượng cụ thể. Giả sử chúng ta có 2 luồng (Thread-1Thread-2) và chỉ có mộtMyClass myClassđối tượng. Trong trường hợp này, nếuThread-1gọimyClass.swap()phương thức, mutex của đối tượng sẽ bận và khi cố gắng gọimyClass.swap()phương thứcThread-2sẽ bị treo trong khi chờ đợi mutex được giải phóng. Nếu chúng ta có 2 luồng và 2MyClassđối tượng (myClass1myClass2), 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 trên các đối tượng khác nhau. Chủ đề đầu tiên thực hiện điều này:

myClass1.swap();
Cái thứ hai thực hiện điều này:

myClass2.swap();
Trong trường hợp này, synchronizedtừ khóa bên trong swap()phương thức sẽ không ảnh hưởng đến hoạt động của chương trình, vì việc đồng bộ hóa được thực hiện bằng 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 chủ đề 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 lấy một cái không phụ thuộc vào việc lấy cái kia .

Các tính năng đặc biệt của đồng bộ hóa trong các 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 = "Ally";
   private static String name2 = "Lena";

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

}
Không rõ mutex sẽ đóng vai trò gì ở đây. Rốt cuộc, chúng tôi đã xác định rằng mỗi đối tượng có một mutex. Nhưng vấn đề là chúng ta không cần các đối tượng để gọi phương MyClass.swap()thức: phương thức này là tĩnh! Vì vậy, những gì tiếp theo? :/ Thực sự không có vấn đề gì ở đây cả. Những người tạo ra Java đã lo liệu mọi thứ :) Nếu một phương thức chứa logic đồng thời quan trọng là tĩnh, thì việc đồng bộ hóa được thực hiện ở cấp độ lớp. Để rõ ràng hơn, chúng ta có thể viết lại đoạn mã trên như sau:

class MyClass {
   private static String name1 = "Ally";
   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ự nghĩ ra điều này: Bởi vì không có đối tượng, cơ chế đồng bộ hóa bằng cách nào đó phải được đưa vào chính lớp đó. Và đó là cách nó diễn ra: chúng ta có thể sử dụng các lớp để đồng bộ hóa.