Chapter 17. Threads and Locks
Chương này tập trung vào bảo toàn dữ liệu (memory consistency), đồng bộ hóa (synchronization), và mô hình bộ nhớ của Java (Java Memory Model - JMM). Đây là nền tảng quan trọng giúp Java hỗ trợ lập trình đa luồng (multithreading) an toàn và hiệu quả.
LƯU Ý: SẼ CẦN MỘT BÀI VIẾT KHÁC ĐÀO SÂU HƠN VỀ CÁC CHỦ ĐỀ THREADS, PROCESS, ĐA LUỒNg, ĐỒNG BỘ VÀ BẤT ĐỒNG BỘ HAY LOCKING. NHỮNG CHỦ ĐỀ NÀY RẤT PHỨC TẠP KHÔNG THỂ NÓI ĐƠN GIẢN TRONG VÀI CÂU.
1️⃣ Các khái niệm quan trọng trong Java Memory Model (JMM)
🔹 Biến dùng chung (Shared Variables)
Khi nhiều luồng cùng truy cập một biến, cần đảm bảo dữ liệu được đọc/ghi một cách an toàn. Nếu không đồng bộ hóa, một luồng có thể đọc dữ liệu chưa cập nhật từ bộ nhớ đệm của CPU, dẫn đến lỗi.
📌 Ví dụ lỗi khi không đồng bộ hóa biến dùng chung:
🔹 Happens-Before (Quan Hệ Xảy Ra Trước)
JMM quy định thứ tự thực thi trong đa luồng bằng happens-before. Nếu một hành động happens-before hành động khác, giá trị từ hành động trước phải được nhìn thấy bởi hành động sau.
📌 Các quy tắc happens-before quan trọng:
Khởi tạo đối tượng (
new
) xảy ra trước bất kỳ truy cập nào vào đối tượng đó.Gọi
start()
trên một thread xảy ra trước khi thread đó thực thi.Gọi
join()
trên một thread xảy ra trước khijoin()
kết thúc.Ghi vào biến
volatile
xảy ra trước khi bất kỳ luồng nào đọc nó.
2️⃣ Từ khóa volatile
volatile
volatile
đảm bảo mọi luồng đều thấy giá trị mới nhất của biến.
Giải pháp của
volatile
: Khi một biến được khai báo làvolatile
, Java Memory Model (JMM) đảm bảo rằng:Khi một luồng ghi (write) giá trị vào biến
volatile
, giá trị đó sẽ được ghi trực tiếp vào bộ nhớ chính (main memory) ngay lập tức.Khi một luồng đọc (read) giá trị từ biến
volatile
, nó sẽ luôn đọc giá trị mới nhất từ bộ nhớ chính, thay vì sử dụng bản sao có thể đã lỗi thời trong cache của nó.
Tóm lại:
volatile
đảm bảo rằng tất cả các luồng sẽ nhìn thấy giá trị mới nhất của biến ngay sau khi nó được một luồng khác thay đổi. Nó giúp giải quyết vấn đề về tính khả kiến giữa các luồng.
📌 Ví dụ không dùng volatile
(có thể gặp lỗi):
📌 Giải pháp với volatile
:
💡 Lưu ý: volatile
không thay thế được synchronized
khi nhiều luồng cần cập nhật biến cùng lúc!
3️⃣ Đồng bộ hóa với synchronized
synchronized
volatile
chỉ giúp đọc/ghi an toàn, nhưng không đảm bảo cập nhật dữ liệu đồng thời. Khi nhiều luồng cùng thay đổi một biến, ta cần synchronized
.
📌 Ví dụ lỗi khi không đồng bộ hóa:
📌 Cách sửa với synchronized
:
4️⃣ Đồng bộ hóa bằng Lock
(ReentrantLock)
Lock
(ReentrantLock)Lock
giúp kiểm soát đồng bộ hóa linh hoạt hơn synchronized
.
📌 Ví dụ sử dụng ReentrantLock
:
💡 Ưu điểm của Lock
so với synchronized
:
Thử lấy khóa (
tryLock()
) mà không bị chặn.Giảm độ trễ nếu khóa đã được giữ bởi luồng khác.
Hỗ trợ nhiều loại khóa như fair lock (ưu tiên luồng chờ lâu hơn).
Biến final
trong môi trường đa luồng
final
trong môi trường đa luồngMột biến final
nếu được gán giá trị trong constructor và không bị thay đổi, Java đảm bảo giá trị đó luôn thấy đúng trong các luồng khác mà không cần volatile
.
6️⃣ Thread Safety với ThreadLocal
ThreadLocal
Nếu mỗi luồng cần một bản sao riêng của biến thay vì dùng chung, ThreadLocal
là giải pháp tối ưu.
📌 Ví dụ sử dụng ThreadLocal
:
💡 Lợi ích của ThreadLocal
:
Mỗi luồng có bản sao riêng của biến → tránh lỗi xung đột.
Hiệu suất cao hơn vì không cần
synchronized
.
📌 7. Tổng kết
✅ Java Memory Model (JMM) đảm bảo dữ liệu giữa các luồng được nhất quán.
✅ Happens-before xác định thứ tự thực thi giữa các luồng.
✅ volatile
đảm bảo đọc/ghi an toàn, nhưng không tránh được cập nhật xung đột.
✅ synchronized
hoặc Lock
giúp bảo vệ dữ liệu khi nhiều luồng cùng thay đổi.
✅ ThreadLocal
giúp mỗi luồng có bản sao riêng, tránh xung đột.
Last updated