Java - Khóa Đồng Bộ Hàm

Xin chào mọi người, những siêu anh hùng Java tương lai! ? Hôm nay, chúng ta sẽ bắt đầu hành trình thú vị vào thế giới Khóa Đồng Bộ Hàm của Java. Đừng lo nếu bạn mới bắt đầu học lập trình; tôi sẽ hướng dẫn bạn qua chủ đề này bước bước, như thế tôi đã làm cho nhiều học viên khác trong những năm dạy học. Vậy, hãy lấy ly đồ uống yêu thích của bạn, thoải mái ngồi, và hãy bắt đầu!

Java - Block Synchronization

Hiểu Về Cơ Bản

Trước khi bước vào khóa đồng bộ hàm, hãy nhanh chóng tóm tắt một số khái niệm cơ bản. Hãy tưởng tượng bạn đang ở trong bếp với những người bạn, mọi người đang cố gắng nấu một bữa ăn cùng nhau. Đó tương tự như các luồng trong Java làm việc cùng nhau trong một chương trình. Đôi khi, bạn cần phối hợp để tránh sự hỗn loạn - đó là nơi synchronization ra đời!

Multithreading Là Gì?

Multithreading như có nhiều đầu bếp trong bếp, mỗi người đang làm việc trên các nhiệm vụ khác nhau đồng thời. Trong Java, những "đầu bếp" này được gọi là luồng, và chúng cho phép chương trình của chúng ta làm nhiều việc cùng một lúc.

Tại Sao Chúng Ta Cần Synchronization?

Hãy tưởng tượng: Bạn và người bạn của bạn đều đặt tay lên bình muối cùng một lúc. Uh-oh! Đó là "tình huống cạnh tranh" trong lập trình. Synchronization giúp ngăn chặn các xung đột này bằng cách đảm bảo chỉ một luồng có thể truy cập vào tài nguyên chia sẻ vào một thời điểm.

Khóa Đồng Bộ Hàm Trong Java

Bây giờ, hãy tập trung vào chủ đề chính của chúng ta: Khóa Đồng Bộ Hàm. Đó là cách để đảm bảo rằng chỉ một luồng có thể thực thi một khối mã cụ thể vào một thời điểm.

Làm Thế Nào Nó Hoạt Động?

Khóa đồng bộ hàm sử dụng từ khóa synchronized theo sau là dấu ngoặc đơn chứa một đối tượng đóng vai trò là khóa. Chỉ một luồng có thể giữ khóa này vào một thời điểm, đảm bảo truy cập duy nhất vào khối đồng bộ.

Hãy xem một ví dụ đơn giản:

public class Đếm {
private int count = 0;

public void tăng() {
synchronized(this) {
count++;
}
}

public int getCount() {
return count;
}
}

Trong ví dụ này, phương thức tăng() sử dụng khóa đồng bộ hàm. Từ khóa this đề cập đến đối tượng hiện tại, đóng vai trò là khóa.

Tại Sao Sử Dụng Khóa Đồng Bộ Hàm?

Khóa đồng bộ hàm linh hoạt hơn synchronization tại cấp độ phương thức. Nó cho phép bạn đồng bộ chỉ những phần quan trọng của mã của bạn, có thể cải thiện hiệu suất.

Ví Dụ Multithreading Không Đồng Bộ

Hãy xem điều gì xảy ra khi chúng ta không sử dụng synchronization:

public class ĐếmKhôngAnToàn {
private int count = 0;

public void tăng() {
count++;
}

public int getCount() {
return count;
}

public static void main(String[] args) throws InterruptedException {
ĐếmKhôngAnToàn counter = new ĐếmKhôngAnToàn();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.tăng();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.tăng();
}
});

t1.start();
t2.start();
t1.join();
t2.join();

System.out.println("Số cuối cùng: " + counter.getCount());
}
}

Nếu bạn chạy mã này nhiều lần, bạn sẽ nhận được các kết quả khác nhau và ít khi là 2000. Điều này là vì các luồng đang gây trở ngại cho nhau.

Ví Dụ Multithreading Với Đồng Bộ Hàm Tại Cấp Độ Khối

Bây giờ, hãy sửa chữa đếm của chúng ta bằng cách sử dụng khóa đồng bộ hàm:

public class ĐếmAnToàn {
private int count = 0;
private Object lock = new Object(); // Chúng ta sẽ sử dụng điều này làm khóa

public void tăng() {
synchronized(lock) {
count++;
}
}

public int getCount() {
return count;
}

public static void main(String[] args) throws InterruptedException {
ĐếmAnToàn counter = new ĐếmAnToàn();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.tăng();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.tăng();
}
});

t1.start();
t2.start();
t1.join();
t2.join();

System.out.println("Số cuối cùng: " + counter.getCount());
}
}

Bây giờ, không giữa khi bạn chạy mã này mấy lần, bạn luôn nhận được 2000 làm số cuối cùng. Đó là sức mạnh của synchronization!

Ví Dụ Multithreading Với Đồng Bộ Hàm Tại Cấp Độ Phương Thức

Để so sánh, đây là cách chúng ta có thể đạt được kết quả tương tự bằng cách sử dụng synchronization tại cấp độ phương thức:

public class ĐếmSynchronizedPhươngThức {
private int count = 0;

public synchronized void tăng() {
count++;
}

public int getCount() {
return count;
}

public static void main(String[] args) throws InterruptedException {
ĐếmSynchronizedPhươngThức counter = new ĐếmSynchronizedPhươngThức();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.tăng();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.tăng();
}
});

t1.start();
t2.start();
t1.join();
t2.join();

System.out.println("Số cuối cùng: " + counter.getCount());
}
}

Cách tiếp cận này cũng hoạt động, nhưng nó đồng bộ toàn bộ phương thức, có thể là quá nhiều nếu chỉ một phần nhỏ của phương thức cần đồng bộ.

So Sánh Các Kỹ Thuật Đồng Bộ

Dưới đây là so sánh nhanh các kỹ thuật đồng bộ mà chúng ta đã thảo luận:

Kỹ Thuật Ưu Điểm Nhược Điểm
Không Đồng Bộ Nhanh, nhưng không an toàn cho tài nguyên chia sẻ Có thể dẫn đến tình huống cạnh tranh và kết quả không nhất quán
Đồng Bộ Hàm Kiểm soát tinh chỉnh, có thể cải thiện hiệu suất Cần chọn đúng vị trí cho các khối đồng bộ
Đồng Bộ Phương Thức Dễ thực hiện Có thể quá đồng bộ, có thể giảm hiệu suất

Kết Luận

Và thế là, các bạn đã hoàn thành hành trình trong thế giới Khóa Đồng Bộ Hàm của Java. Hãy nhớ, synchronization như là các đèn giao thông trong một thành phố rộng lớn - nó giúp quản lý luồng và ngăn chặn tai nạn. Sử dụng nó một cách khôn ngoan, và các chương trình đa luồng của bạn sẽ chạy mượt mà và an toàn.

Khi tiếp tục hành trình Java của bạn, hãy tiếp tục thực hành các khái niệm này. Thử tạo ra các ứng dụng đa luồng của riêng bạn và thử nghiệm các kỹ thuật đồng bộ khác nhau. Ai biết? Bạn có thể sẽ tạo ra ứng dụng đa luồng tiếp theo thay đổi thế giới!

Chúc mãi mãi mã của bạn luôn được đồng bộ! ?

Credits: Image by storyset