Phân biệt parallelism và concurrency trong Java

Chào mừng các bạn đã quay trở lại với thachleblog.com, bài viết hôm nay mình sẽ trình bày sự khác nhau giữa hai khái niệm concurrencyparallelism trong lập trình multi thread. Việc hiểu rõ được đặc điểm của concurrency và parallelism sẽ giúp chúng ta hiểu được cơ chế làm việc của các thread khi đồng thời thực hiện các task (công việc) của ứng dụng, từ đó lựa chọn phù hợp cho thiết kế của project. Vậy, đặc điểm của concurrency và parallelism là như thế nào, chúng ta cùng bắt đầu tìm hiểu nhé.

Concurrency

Concurrency có nghĩa là ứng dụng có khả năng thực hiện nhiều task  tại một thời điểm.

Cần lưu ý là trên một CPU (CPU 1 core) chỉ có thể chạy 1 thread tại một thời điểm, nhưng có thể switch (chuyển giao qua lại) rất nhanh giữa các thread do đó ta có cảm giác nhiều thread đang chạy tại một thời điểm.

Ví dụ concurrency: trên trình duyệt (browser) ta có thể vừa tải file, vừa nghe nhạc, vừa blog… :mrgreen: . Ta có thể hiểu trình duyệt sử dụng cơ chế concurrency.

Phân biệt parallelism và concurrency

Parallelism

Parallelism có nghĩa là một ứng dụng có thể chia task thành các sub-task chạy song song trên các CPU. Đối với CPU 1 core, ứng dụng không thể chạy theo cơ chế parallelism.

Ví dụ: trên ứng dụng download manger hoặc download với trình duyệt Cờ Rôm +, ta có thể thấy file được chia thành nhiều “đoạn” để tải song. Mỗi đoạn ta có thể hiểu đó là 1 thread đang chạy. Và đó là cơ chế parallelism

Phân biệt parallelism và concurrency

Concurrency và Paralleism

Như chúng ta có thể thấy, concurrency liên quan đến cách mà ứng dụng handles multiple tasks. Một ứng dụng có thể xử lý một task tại một thời điểm (sequentially) hoặc nhiều task tại một thời điểm (concurrency).

Parallelism liên quan đến cách mà ứng dụng handles từng task riêng biệt. Một ứng dụng có thể xử lý toàn bộ task từ đầu đến cuối, hoặc chia task thành nhiều task con và có thể được xử lý đồng thời.

Một ứng dụng có thể sử dụng cơ chế concurrency nhưng không có parallelism hoặc ngược lại. Ứng dụng có thể không cần concurrency và parallelism và cũng có thể ứng dụng có cả concurrency và parallel, tùy vào yêu cầu business của ứng dụng. 

Tuy nhiên trong trường hợp kết hợp cần chú ý về khả năng xử lý của CPU vì có khả năng làm mất đi ưu điểm của concurrency hoặc parallelism dẫn đến ảnh hưởng performance… 

Việc hiểu rõ cơ chế làm việc của các thread sẽ rất hữu ích trong khi thiết kế multi thread trong ứng dụng. Mong rằng bài viết của mình sẽ giúp ích và tạo cảm hứng cho các bạn. Mọi ý kiến đóng góp vui lòng comment bên dưới, mình sẽ follow. Hẹn gặp lại các bạn ở các bài viết sau.

Xem thêm:

http://tutorials.jenkov.com/java-concurrency/concurrency-vs-parallelism.html
http://howtodoinjava.com/core-java/multi-threading/concurrency-vs-parallelism

Deadlock trong Java

Chào mừng các bạn đã trở lại với thachleblog, hôm nay mình sẽ giới thiệu một chủ đề khá thú vị trong lập tình multi thread, đó là deadlock. Vậy deadlock là gì, làm thế nào để phát hiện deadlock? chúng ta cùng bắt đầu nhé.

Giới thiệu

Ở bài trước, khi sử dụng synchronization, thread sẽ chiếm giữ lock object để đảm bảo tại 1 thời điểm, chỉ có 1 thread được thực thi đoạn block code…

Vấn đề nảy sinh khi 2 hoặc nhiều thread giữ block object mà thread kia đang cần, dẫn đến tình trạng lock lẫn nhau

deadlock trong java

Hình minh họa tình huống dẫn đến deadlock (nguồn internet)

Vấn đề này chính là deadlock

Ví dụ

Chúng ta cùng xem code ví dụ xem điều gì xảy ra khi dealock nhé. Doạn code minh họa việc sử dụng synchronization và gây ra deadlock (tham khảo internet)

  1. package thach.le.deadlock;
  2. public class Task {
  3. private Object key1 = new Object();
  4. private Object key2 = new Object();
  5. public void task1() {
  6. synchronized (key1) {
  7. System.out.println("["+ Thread.currentThread().getName() + "]" + "I am doing task1");
  8. task2();
  9. }
  10. }
  11. public void task2() {
  12. synchronized (key2) {
  13. System.out.println("["+ Thread.currentThread().getName() + "]" + "I am doing task2");
  14. task3();
  15. }
  16. }
  17. public void task3() {
  18. synchronized (key1) {
  19. System.out.println("["+ Thread.currentThread().getName() + "]" + "I am doing task3");
  20. }
  21. }
  22.  
  23. public static void main(String[] args) throws InterruptedException {
  24. Task task = new Task();
  25. Runnable runable1 = () -> task.task1();
  26. Runnable runable2 = () -> task.task2();
  27. Thread thread1 = new Thread(runable1);
  28. thread1.start();
  29. Thread thread2 = new Thread(runable2);
  30. thread2.start();
  31. thread1.join();
  32. thread2.join();
  33. }
  34. }

Ở đoạn code trên mình có 2 thread:

  • Thread thứ nhất chạy method task1, method task1 có lock object là key1 đồng thời gọi method task2
  • Thread thứ hai chạy method task2, task 2 có lock object là key2 đồng thời gọi task3, task3 có lock object là key1

=> Thread thứ nhất có lock object là key1 và cần lock object key2 từ thread thứ hai, đồng thời thread thứ 2 có lock object là key2 và cần lock object là key1 từ thread 1 (hình minh họa ở trên)

Kết quả khi xảy ra deadlock

result

Chương trình bị “treo” (hai thread chờ nhau đến vô tận)
Chúng ta cùng debug để xem rõ hơn nhé:

debugdeadlock

Từ màn hình debug ta có thể thấy tình trạng của 2 thread-0 và thread-1:

  • Thread-0 đang nắm giữ Object id = 28 (key1) và đang đợi Object id = 30 (key1)
  • Thread-1 đang nắm giữ Object id = 30 (key2) và đang đợi Object id = 28 (key2

Làm thế nào để phát hiện deadlock?

Nếu không debug, chúng ta vẫn có thể phát hiện được deadlock bằng sử dụng jstack (trong thư mục bin của Java) bằng cách vào command line, gõ jstack – pid (pid của chương trình Java, trong Windows có thể xem trong Task Manager) xem thêm

Kết quả khi chạy jstack:

  1. Found one Java-level deadlock:
  2. =============================
  3. "Thread-1":
  4. waiting to lock monitor 0x00000000181f8a18 (object 0x00000000e096d858, a java.lang.Object),
  5. which is held by "Thread-0"
  6. "Thread-0":
  7. waiting to lock monitor 0x0000000015ddf518 (object 0x00000000e096d868, a java.lang.Object),
  8. which is held by "Thread-1"
  9.  
  10. Java stack information for the threads listed above:
  11. ===================================================
  12. "Thread-1":
  13. at thach.le.deadlock.Task.task3(Task.java:24)
  14. - waiting to lock (a java.lang.Object)
  15. at thach.le.deadlock.Task.task2(Task.java:18)
  16. - locked (a java.lang.Object)
  17. at thach.le.deadlock.Task.lambda$1(Task.java:32)
  18. at thach.le.deadlock.Task$$Lambda$2/1359044626.run(Unknown Source)
  19. at java.lang.Thread.run(Unknown Source)
  20. "Thread-0":
  21. at thach.le.deadlock.Task.task2(Task.java:17)
  22. - waiting to lock (a java.lang.Object)
  23. at thach.le.deadlock.Task.task1(Task.java:11)
  24. - locked (a java.lang.Object)
  25. at thach.le.deadlock.Task.lambda$0(Task.java:31)
  26. at thach.le.deadlock.Task$$Lambda$1/665576141.run(Unknown Source)
  27. at java.lang.Thread.run(Unknown Source)
  28.  
  29. Found 1 deadlock.

Từ kết quả in ra sau khi chạy jstack. Chúng ta có thể đọc được và xác định được đoạn code gây ra deadlock từ đó có biện pháp thích hợp để ngăn chặn. Cách thông thường là tránh trường hợp các object phải đợi lẫn nhau (có thể cho thread đợi thread khác thực thi xong).

Deadlock là một chủ đề khó trong Java, do đó, qua bài chia sẻ này, mình mong rằng có thể giúp các bạn hiểu hơn về deadlock. 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/deadlock.html