Java là một ngôn ngữ mạnh trong việc xử lý đa luồng, đa tiến trình nhưng cũng vì đó mà khi phát triển phần mềm bằng ngôn ngữ này, chúng ta thường xuyên gặp phải nhiều vấn đề khi triển khai chương trình trong môi trường đa luồng. Dữ liệu của bạn có thể bị sai, thứ tự thực hiện công việc gây ảnh hưởng lớn tới dịch vụ mà ta đang hướng tới. Để có thể làm chủ được các vấn đề này, chúng ta cần nắm được các kiến thức nhất định về cơ chế quản lý tiến trình của JVM.

Trong bài viết này, Stringee sẽ chia sẻ cho các bạn các kiến thức cần thiết về một pattern quan trọng trong Java đó là monitor object và cách quản lý thứ tự thực hiện của các tiến trình.

1. Monitor object trong Java là gì ?

"Monitor object" thực ra không phải một thuật ngữ chính thức trong ngôn ngữ mà chúng ta có thể hiểu nó liên quan đến hai khái niệm là “monitor” và “object” trong ngữ cảnh đa luồng.

Trong lập trình đa luồng, monitor là cơ chế được sử dụng để đảm bảo đồng bộ hóa truy cập vào các phần tài nguyên được chia sẻ giữa các thread. Monitor cung cấp cơ chế để đảm bảo tại một thời điểm chỉ có một luồng được tác động vào tài nguyên đang được chia sẻ. Điều này đảm bảo các thay đổi được thực hiện trên các biến sẽ được kiểm soát và không xảy ra xung đột

Dưới đây là mô hình diễn tả quá trình monitor hoạt động trong ngôn ngữ Java:

Dựa vào hình vẽ, ta có thể thấy khi một luồng bắt đầu truy cập vào một nguồn tài nguyên nào đó, nó sẽ được đưa và một entrySet để chờ được vào xử lý tài nguyên. Có thể hiểu đây như một phòng chờ và các thread đang xếp hàng để được vào phòng chính. Sau khi qua hàng chờ, thread sẽ lấy lock để chiếm quyền tác động vào tài nguyên. Nếu luồng này thực hiện wait, nó sẽ bị đẩy ra waitSet và quá thời gian này mà không tác động vào tài nguyên thì nó sẽ bị đưa về entrySet. Sau khi lấy được lock, thread sẽ vào trạng thái 4 tức là sử dụng tài nguyên đã được chia sẻ. Sau khi sử dụng xong, nó sẽ cần thực hiện giải phóng lock đang giữ ở bước 5 và ra khỏi vòng quản lý của monitor object.

2. Quản lý thứ tự thực hiện các tiến trình

2.1. Quản lý tiến trình bằng cách nào ?

Để quản lý thứ tự thực hiện các tiến trình, Java cung cấp cho chúng ta nhiều cách thức để thực hiện việc này. Chúng ta có thể sử dụng từ khóa synchronized(implicit lock) hoặc dùng lớp Reentrantlock(explicit lock). Trong bài này, Stringee và các bạn sẽ giới thiệu tới các bạn cách quản lý thứ tự thực hiện tiến trình băng từ khóa synchronized.

2.2. Tại sao lại chọn sử dụng synchronized để đồng bộ hóa các tiến trình ?

Chúng ta đã biết monitor object trong java là gì, vậy, nó có liên quan gì đến từ khóa “synchronized”. Điều này có thể được lý giải như sau:

Trong Java, mọi đối tượng (object) đều có một monitor tương ứng. Monitor này được sử dụng trong cơ chế đồng bộ hóa với từ khóa synchronized. Khi một phương thức hoặc một khối được đánh dấu bởi synchronized, nó chỉ cho phép một luồng thực thi được phép truy cập vào đối tượng đó tại một thời điểm. Nếu một luồng đang thực thi trong một phương thức synchronized, thì các luồng khác phải chờ đợi cho đến khi luồng hiện tại hoàn thành hoặc được giải phóng khỏi monitor.

2.3. Synchronized Instance Methods

Một instance method đã được synchronized tức là nó đã được synchronized cho đối tượng sở hữu phương thức đó. Vì vậy, mỗi đối tượng có các phương thức được synchronized trên mỗi object khác nhau, đây được gọi là: the owning instance.

Chỉ duy nhất một thread trên một đối tượng có thể thực thi trong một method trên. Nếu nhiều hơn một đối đối tượng tồn tại thì một chỉ có một thread tại một thời điểm có thể thực thi một synchronized method trên một đối tượng. Mỗi thread một đối tượng.

Trong ví dụ dưới đây, ta có thể thấy method add() đã được synchronized

public class MyCounter {

    private int count = 0;

    public synchronized void add(int value) {
        this.count += value;
    }

}

Các điều ở trên còn đúng với toàn bộ các synchronized method khác của đối tượng. Vì vậy, trong ví dụ dưới đây ta có thể thấy chỉ duy nhất một thread có thể thực thi 1 trong 2 method đã được synchronized.

public class MyCounter {

    private int count = 0;

    public synchronized void add(int value) {
        this.count += value;
    }

    public synchronized void subtract(int value) {
        this.count -= value;
    }

}

2.4. Synchronized Static Methods

Static methods được coi là synchronized cũng giống như là instance methods sử dụng từ khóa synchronized . Dưới đây là một ví dụ:

public static MyStaticCounter{

    private static int count = 0;

      public static synchronized void add(int value){
        count += value;
    }

}

Synchronized static methods được synchronized cho class có chứa các method này. Vì các lớp(class) trong JVM là duy nhất cho nên chỉ có duy nhất một thread có thể thực thi static synchronized method tại cùng một class

Nếu một class có hai hoặc nhiều hơn static methods được đánh dấu là synchronized thì chỉ duy nhất một thread có thể thực thi một trong các methods này ở một thời điểm.

2.5. Synchronized Blocks in Instance Methods

Trên thực tế chúng ta không cần phải synchronized cả một phương thức như đã làm ở phần trên. Đôi khi, ta chỉ nên thực hiện synchronize một phần của phương thức. Đây là khi chúng ta sẽ sử dụng đến synchronized blocks trong một phương thức.

Dưới đây là một ví dụ về block trong instance method

public void add(int value){

        synchronized(this){
           this.count += value;
        }

 }

Ví dụ này cho ta thấy việc synchronize một block trong một phương thức. Đoạn code này sẽ được thực thi như thể nó là một phương thức đã được synchronized.

Với ví dụ trên, ta đã thực hiện khởi tạo một synchronized block bằng việc thêm một đối tượng vào trong dấu ngoặc đơn. Tại đây, đối tượng “this” đã được sử dụng, đây là đối tượng sẽ được sử dụng để thực thi khi method này được gọi và nó được gọi là một monitor object. Đoạn code này được coi là đã được synchronized đối với monitor object. Một đối tượng đã được synchronized sẽ dùng class nó thuộc về như là một monitor object.

Chỉ một thread có thể thực thi một synchronized block với cùng chung một monitor object

Việc synchronized một block với toàn bộ nội dung thực thi trong phương thức sẽ tương tự như việc synchronized cả một phương thức.

Việc synchronized blocks có thể được thực thi trong một static method. Dưới đây là ví dụ từ phần trước được chuyển sang dạng static. Cả hai method đều được synchronized trên class object mà class methods này thuộc trong nó, và vì nó đã được synchronized nên một thread chỉ có thể thực hiện một trong hai method này tại một thời điểm.

public static synchronized void add(int value){
           this.count += value;
 }

public static void add(int value){
        synchronized(Ex.class){
           this.count += value;
        }

 }

Kết bài

Trên đây là một giới thiệu về thuật ngữ monitor object trong Java cũng như cách áp dụng nó vào trong việc phát triển phần mềm bằng ngôn ngữ Java. Sử dụng synchronized là một trong những cách đơn giản và mạnh mẽ để đảm bảo đồng bộ thread trong Java. Tuy nhiên, cần lưu ý rằng có nhiều cơ chế khác nhau để đạt được đồng bộ hóa thread hiệu quả trong Java, ngoài ra còn nhiều cách khác để đạt được đồng bộ hóa thread trong Java.

Stringee API cung cấp các tính năng như gọi thoại, gọi video, tin nhắn chat, SMS hay tổng đài chăm sóc khách hàng (CSKH) có thể được nhúng trực tiếp vào các ứng dụng/website của doanh nghiệp nhanh chóng. Điều này giúp tiết kiệm đến 80% thời gian và chi phí cho doanh nghiệp, trong khi nếu tự phát triển các tính năng này có thể mất từ 1 - 3 năm.