SSV
+11

Java Synchronized Blocks

Khi chúng ta bắt đầu 2 hay nhiều Thread trong cùng 1 chương trình, có thể xảy ra tình huống nhiều Thread cố gắng truy cập vào cùng 1 file hay 1 đối tượng nhất định gây ra tình trạng xung đột dữ liệu, mất dữ liệu.

Ví dụ, nếu nhiều luồng cố gắng để ghi vào cùng một tập tin, vì vậy tập tin có thể bị hỏng dữ liệu vì một trong những Thread có thể ghi đè dữ liệu trong khi một thread khác cũng mở cùng một tập tin, cùng lúc đó một thread khác nữa có thể được đóng tập tin đó.

Vì vậy, sinh ra nhu cầu để đồng bộ hóa các hành động của nhiều thread khác nhau (multi-thread) và chắc chắn rằng chỉ có một thread có thể truy cập các tài nguyên tại một thời điểm nào đó. Điều này được thực hiện bằng cách sử dụng một khái niệm gọi là monitors (giám sát).

Ngôn ngữ lập trình Java cung cấp một cách rất tiện lợi cho việc tạo ra Thread và đồng bộ hóa các nhiệm vụ bằng cách sử dụng các khối synchronized (đồng bộ). Bạn giữ các nguồn tài nguyên được chia sẻ trong khối này.

Một khối Synchronized block đánh dấu một phương thức hay một khối mã là được đồng bộ. Sử dụng khối đồng bộ trong Java có thể tránh xảy ra xung đột.

Chạy nhiều hơn một thread bên trong cùng một ứng dụng không tự gây ra vấn đề. Các vấn đề chỉ phát sinh khi nhiều Thread cùng truy cập vào các nguồn tài nguyên. Ví dụ cùng một bộ nhớ (biến, mảng, hay đối tượng), các hệ thống (cơ sở dữ liệu, dịch vụ web, vv) hoặc các tập tin. Trong thực tế, vấn đề chỉ phát sinh nếu một hoặc nhiều hơn các thread cùng ghi tới các tài nguyên này. Nó là an toàn để cho nhiều Thread cùng đọc cùng một tài nguyên, miễn là không thay đổi chúng.

Sau đây là cú pháp sử dụng synchronized block (khối đồng bộ):

synchronized(objectidentifier) {
   // Access shared variables and other shared resources
}

Ở đây, objectidentifier là một tham chiếu đến một đối tượng và tuyên bố đại diện cho đồng bộ

Một khối đồng bộ trong Java đồng bộ được trên một số đối tượng. Tất cả các khối đồng bộ đồng bộ trên cùng một đối tượng chỉ có thể cùng một lúc có một thread thực thi bên trong chúng. Tất cả các thread khác cố gắng để nhập khối đồng bộ bị chặn cho đến khi thread bên trong khối đồng bộ thoát khối.

Các từ khóa synchronized có thể được sử dụng để đánh dấu 4 kiểu khác nhau của các khối:

  1. Phương thức thông thường
  2. Phương thức tĩnh (static method)
  3. Khối code (Synchronized blocks) được đồng bộ bên trong phương thức
  4. Khối code được đồng bộ bên trong phương thức tĩnh

Các khối này được đồng bộ trên các đối tượng khác nhau. Các loại hình khối đồng bộ bạn cần phụ thuộc vào tình hình cụ thể để sử dụng.

1. Đồng bộ các phương thức thông thường

Đây là ví dụ đồng bộ phương thức:

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

Chú ý việc sử dụng các từ khóa synchronized (đồng bộ) để khai báo phương thức. Điều này thông báo cho Java rằng phương thức này sẽ được đồng bộ.

Một phương thức thức đồng bộ trong Java được đồng bộ trên thể hiển của đối tượng sở hữu chúng. Vì thế mỗi thể hiện của đối tượng có các phương thức đồng bộ riêng và được đồng bộ riêng rẽ trên từng thể hiện (instance) của chúng. Chỉ có một thread có thể thực hiện bên trong một phương thức đồng bộ. Nếu có nhiều hơn 1 thể hiển (instance object) tồn tại, sau đó chỉ có 1 thread tại 1 thời điểm điểm có thể thực thi bên trong phương thức được đồng bộ cho mỗi thể hiện. Vì vậy “một thread trên một thể hiện”.

2. Đồng bộ các phương thức tĩnh (Static)

Phương thức tĩnh được đánh dấu là đồng bộ có cú pháp cũng giống như phương thức thông thường bằng cách sử dụng từ khóa synchronized. Dưới đây là một ví dụ phương thức tĩnh được đồng bộ

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

Cũng như vậy sử dụng synchronized ở đây tuyên bố phương thức này là đồng bộ.

Lưu ý: nếu bạn thực hiện đánh dấu phương thức tĩnh là đồng bộ, phương thức sẽ được khóa (lock) trên class không phải object. Tức là "tại 1 thời điểm chỉ 1 thread được chạy trên 1 class“.

Ví dụ:

class Table {

  synchronized static void printTable(String name, int n) {
		for (int i = 1; i <= 5; i++) {
			System.out.println(name + ": " + (n * i));
			try {
				Thread.sleep(100);
			} catch (Exception e) {
			}
		}
	}
}

class MyThread1 extends Thread {
	private String name;
	public MyThread1(String name) {
		this.name = name;
	}
	public void run() {
		Table.printTable(name, 1);
	}
}

class MyThread2 extends Thread {
	private String name;
	public MyThread2(String name) {
		this.name = name;
	}

	public void run() {
		Table.printTable(name, 10);
	}
}

public class JavaThreadSynchronized1 {

	public static void main(String[] args) {
		MyThread1 t1 = new MyThread1("Thread 1");
		MyThread2 t2 = new MyThread2("Thread 2");
		MyThread1 t3 = new MyThread1("Thread 1(2)");
		t1.start();
		t2.start();
		t3.start();
	}
}

Kết quả:

Thread 1: 1
Thread 1: 2
Thread 1: 3
Thread 1: 4
Thread 1: 5
Thread 2: 10
Thread 2: 20
Thread 2: 30
Thread 2: 40
Thread 2: 50
Thread 1(2): 1
Thread 1(2): 2
Thread 1(2): 3
Thread 1(2): 4
Thread 1(2): 5

Nếu ko khai báo đồng bộ (Synchronized):

Thread 2: 10
Thread 1: 1
Thread 1(2): 1
Thread 2: 20
Thread 1: 2
Thread 1(2): 2
Thread 2: 30
Thread 1: 3
Thread 1(2): 3
Thread 1: 4
Thread 2: 40
Thread 1(2): 4
Thread 1: 5
Thread 1(2): 5
Thread 2: 50

3. Khối code (Synchronized blocks) được đồng bộ bên trong phương thức

Nếu bạn không cần đồng bộ toàn bộ phương thức và chỉ muốn đồng bộ từng phần nào đó trong phương thức, Java cung cấp cho bạn khối đồng bộ (Synchronized blocks) để làm điều đó.

Đây là 1 khối đồng bộ bên trong 1 phương thức không đồng bộ (unsynchronized):

public void add(int value){
    //TODO: Do something else

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

Chú ý: Khối mã được đồng bộ phải được đặt trong dấu ngoặc nhọn. Biến “this” thể hiện khối mã sẽ được đồng bộ trên đối tượng (object) sở hữu phương thức chứa chúng. Các đối tượng được đồng bộ được gọi là các đối tượng giám sát (Monitor Object).

Đoạn mã trên được xem là được đồng bộ trên đối tượng giám sát.

Chỉ một Thread được thực thi bên trong 1 Synchronized block trên cùng 1 đối tượng giám sát.

Ví dụ sau đây có thể được gọi là tương đồng với nhau khi sử dụng:

  public class MyClass {

    public synchronized void log1(String msg1, String msg2){
       log.writeln(msg1);
       log.writeln(msg2);
    }

    public void log2(String msg1, String msg2){
       synchronized(this){
          log.writeln(msg1);
          log.writeln(msg2);
       }
    }
  }

4. Khối code được đồng bộ bên trong phương thức tĩnh

2 ví dụ sau đây là tương tự như trên nhưng là đồng bộ với phương thức tĩnh. Khác với khối Synchronized blocks ở phương thức thông thường, khi khai báo đồng bộ trên phương thức tĩnh thì khi đó cùng 1 thời điểm chỉ có 1 thread được thực thi trên 1 class (k phải trên thể hiện của class (object)).

public class MyClass {

    public static synchronized void log1(String msg1, String msg2){
       log.writeln(msg1);
       log.writeln(msg2);
    }

    public static void log2(String msg1, String msg2){
       synchronized(MyClass.class){
          log.writeln(msg1);
          log.writeln(msg2);
       }
    }
 }
5. Ví dụ DEMO
class MyClass5 {

	private int a = 0;

	public synchronized void log1(String msg1, String msg2) {

		for (int i = 1; i <= 5; i++) {
			a++;
			System.out.println(msg1 + "-" + msg2 + " > A: " + a);

			try {
				Thread.sleep(100);
			} catch (Exception e) {
			}
		}
	}

	public void log2(String msg1, String msg2) {
		synchronized (this) {
			for (int i = 1; i <= 5; i++) {
				a++;
				System.out.println(msg1 + "-" + msg2 + " > A: " + a);

				try {
					Thread.sleep(100);
				} catch (Exception e) {
				}
			}
		}
	}
}

public class JavaThreadSynchronized5 {

	public static void main(String[] args) {
		MyClass5 obj1 = new MyClass5();

		Thread t1 = new Thread() {
			public void run() {
				obj1.log1("Thread 1", "Log 1");
				// obj1.log2("Thread 1", "Log 2");
			}
		};

		Thread t2 = new Thread() {
			public void run() {
				obj1.log1("Thread 2", "Log 1");
				// obj1.log2("Thread 2", "Log 2");
			}
		};

		Thread t3 = new Thread() {
			public void run() {
				obj1.log1("Thread 3", "Log 1");
				// obj1.log2("Thread 3", "Log 2");
			}
		};

		t1.start();
		t2.start();
		t3.start();
	}

}

Output:

Thread 1-Log 1 > A: 1
Thread 1-Log 1 > A: 2
Thread 1-Log 1 > A: 3
Thread 1-Log 1 > A: 4
Thread 1-Log 1 > A: 5
Thread 3-Log 1 > A: 6
Thread 3-Log 1 > A: 7
Thread 3-Log 1 > A: 8
Thread 3-Log 1 > A: 9
Thread 3-Log 1 > A: 10
Thread 2-Log 1 > A: 11
Thread 2-Log 1 > A: 12
Thread 2-Log 1 > A: 13
Thread 2-Log 1 > A: 14
Thread 2-Log 1 > A: 15

**6. Tổng kết **

Như vậy sử dụng Java Synchronized Blocks là rất hữu ích trong trường hợp có nhiều luồng (thread) cùng truy cập tới 1 đối tượng, 1 class, hay 1 tài nguyên nào đó. Việc này giúp ta kiểm soát được luồng truy cập cũng như giúp luôồng an tòan và tính chính xác của dữ liệu.

Tham khảo:


All Rights Reserved