Synchronization trong Java

Chào mừng các bạn đã trở lại với thachleblog. Ở bài trước, mình đã đề cập đến vấn đề thread interference khi sử dụng multi thread trong Java. Một trong những giải pháp cho vấn đề này chính là sử dụng synchronization, vậy sử dụng synchronization như thế nào, synchronization hoạt động như thế nào, chúng ta cùng bắt đầu nhé.

Ví dụ sử dụng synchronization để giải quyết thread interference ở ví dụ trước:

  1. package thach.le.synchroniztion;
  2. public class Synchronization {
  3. public static void main(String[] args) throws InterruptedException {
  4. Account counter = new Account();
  5. Runnable runnable = () -> {
  6. counter.increment(1);
  7. };
  8. Thread[] threads = new Thread[1000];
  9. for (int i = 0; i < threads.length; i++) {
  10. threads[i] = new Thread(runnable);
  11. threads[i].start();
  12. }
  13. // wait for all thread are finished
  14. for (int i = 0; i < threads.length; i++) {
  15. threads[i].join();
  16. }
  17. System.out.println("Count: " + counter.value());
  18. }
  19. }
  20.  
  21. class Account {
  22. private Object key = new Object();
  23. private int amount = 0;
  24. public void increment(int num) {
  25. synchronized (key) {
  26. amount = amount + num;
  27. }
  28. }
  29. public synchronized int value() {
  30. return amount;
  31. }
  32. }

Kết quả sẽ luôn là 1000
synchronization trong Java

Synchronization chính là khả năng đảm bảo đoạn code được thực thi bởi duy nhất một thread ở cùng 1 thời điểm.

Ta có thể sử dụng sychronization bằng các cách:

  • Synchronized method: đặt từ khóa synchronized trong method
  • Synchronized block: đặt từ khóa synchronized bên trong method

Synchronization hoạt động như thế nào?

Ở ví dụ trên mình dùng synchronized block, synchronized block sử dụng 1 object dùng để “lock” đoạn code bên trong method  increment() (mình sẽ gọi object này là lock object).

Object này chứa 1 value gọi là key (trong Java tất cả các object đều có key để sử dụng cho synchronization). Khi một thread thực thi đoạn synchronization code, thread đó sẽ chiếm giữ key của lock object, synchronization đảm bảo rằng, các thread chỉ thực thi đoạn code khi key của lock object là available (không có thread nào sử dụng). Do đó, tại 1 thời điểm, chỉ có 1 thread thực thi đoạn synchroniztion code.

Tiến trình đó được mô tả như sau:

  • Lock object có duy nhất 1 key, key này sẽ có trạng thái là available khi không có thread nào sử dụng
  • Khi một thread thực thi đoạn synchronization code, thread đó sẽ nắm giữ key của lock object, key sẽ có trạng thái là in – use
  • Khi một thread thực thi xong đoạn synchronization code, key của lock object trở về trạng thái available cho các thread khác sử dụng

Sự khác nhau giữa synchronized method và synchronized block

Có lẽ đã quá rõ ràng nhưng có lẽ mình cũng cần nói thêm một chút. Dùng synchronized method có nghĩa là cả method sẽ bị block bởi 1 thread khi thread đó thực thi. Cách này đơn giản nhưng đôi lúc hơi cưng nhắc. Do đó, nên ưu tiên dùng synchronized block để giảm tối đa đoạn code bị lock, giảm đến mức thấp nhất tình trạng “thắt nút cổ chai” – tất cả các thread phải “đứng đợi” ngoài method.

Vì đặc điểm đảm bảo chỉ duy nhất một thead được thao tác với dữ liệu và thao tác kiểm tra lock object trong method nên synchronization sẽ ảnh hưởng đến performance của ứng dụng. Do đó, chỉ sử dụng synchronization trong trường lợp share data giữa các thread.

Vừa rồi là phần trình bày của mình về synchronization, sử dụng synchronization sẽ ngăn chặn được khả năng xảy ra thread interference, nhưng sẽ có khả năng gây ra vấn đề khác, vấn đề đó là gì? mình sẽ trình bày ở bài sau, các bạn nhớ theo dõi nhé. Rất mong nhận được ý kiến đóng góp của tất cả các bạn.

Xin cảm ơn.
Tham khảo thêm:
https://app.pluralsight.com/player?course=java-patterns-concurrency-multi-threading
https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html

Thread interference trong Java

Chào mừng các bạn đã trở lại với thachleblog. Ở bài trước mình đã giới thiệụ về multi thread và sự khác nhau giữa sử dụng runable interface và thread class trong Java. Nhưng việc sử dụng multi thread cũng gặp các vấn đề, một trong những vấn đề đó là thread interference hay còn được gọi là race condition. Vậy thread interference là gì? Tác động của nó như thế nào? Chúng ta cùng bắt đầu nhé.

Chúng ta cùng xem ví dụ:

– Class Account có method increament(), method này sẽ thực hiện cộng 1 số nguyên truyền vào cho giá trị amount

  1. package thach.le.threadinterference;
  2. public class Account {
  3. private int amount = 0;
  4. public void increment(int num) {
  5. amount = amount + num;
  6. }
  7. public int value() {
  8. return amount;
  9. }
  10. }
  • Trường hợp 1: ta gọi 1000 lần method increament() mà không sử dụng multi thread:
  1. package thach.le.threadinterference;
  2. public class NonMultiThread {
  3. public static void main(String[] args) {
  4. Account account = new Account();
  5. for (int i = 0; i < 1000; i++){
  6. account.increment(1);
  7. }
  8. System.out.println("Amount: " + account.value());
  9. }
  10. }

Kết quả sẽ luôn là 1000
thread interference trong java
– Trường hợp 2: sử dụng 1000 thread cùng gọi đồng thời method increment(), sử dụng multi thread:

  1. package thach.le.threadinterference;
  2. public class ThreadInterference {
  3. public static void main(String[] args) throws InterruptedException {
  4. Account account= new Account();
  5. Runnable runnable = () -> {
  6. account.increment(1);
  7. };
  8. // use a lot of theads will be easy to see the issue
  9. Thread [] threads = new Thread[1000];
  10. for (int i = 0; i < threads.length; i++) {
  11. threads[i] = new Thread(runnable);
  12. threads[i].start();
  13. }
  14. // wait for all thread are finished
  15. for (int i = 0; i < threads.length; i++) {
  16. threads[i].join();
  17. }
  18. System.out.println("Count: " + account.value());
  19. }
  20. }

Kết quả sẽ là khác nhau ở các lần chạy, có thể là 1000, 999, 998, 997 … Lý do mình sử dụng đến 1000 thread là vì khi sử dụng 2, hoặc số ít thread, khả năng xảy ra vấn đề này rất thấp, mình sử dụng số lượng lớn để chúng ta có thể dễ thấy.

Ví dụ vừa rồi chính là thread interference, thread interference chính là vấn đề không đồng nhất khi xảy ra tranh chấp của dữ liệu dùng chung (share data) giữa các thread. Ở ví dụ trên, khi một thread chiếm giữ biến amount và thực hiện phép toán tăng thêm 1 đơn vị. Nhưng rất có thể ngay tại thời điểm đó, 1 hoặc nhiều thread khác cũng đang thực hiện phép toán tăng thêm một đơn vị với giá trị chưa được cập nhật là 0. Do đó, dẫn đến kết quả sai lệch.

Java cũng đã cung cấp các giải pháp để giải quyết vấn đề này, một trong số đó chính là sử dụng sychronization. Vậy sử dụng synchronization như thế nào? Và sử dụng synchronization có phát sinh vấn đề gì không? chúng ta sẽ cùng tìm hiểu qua bài viết tiếp theo của mình nhé.

Rất mong nhận được ý kiến đóng góp của tất cả các bạn. Xin cảm ơn!

Tham khảo thêm:
https://docs.oracle.com/javase/tutorial/essential/concurrency/interfere.html