images
29/09/2020 08:01 am

Các cách lập trình đa luồng trong Java - Phần 2 (Java Threads and Synchronized)

Lập trình đa luồng là kỹ thuật lập trình rất phổ biến ngày nay khi mỗi máy tính đều hỗ trợ nhiều nhân và nhiều luồng hơn.

Lập trình đa luồng là kỹ thuật lập trình rất phổ biến ngày nay khi mỗi máy tính đều hỗ trợ nhiều nhân và nhiều luồng hơn. Tuy nhiên đây là một kỹ thuật lập trình không dễ vì nhiều yếu tố:


- Đảm bảo dữ liệu được toàn vẹn

- Đảm bảo chương trình hoạt động đúng trình tự mong muốn

- Đảm bảo hiệu năng chương trình được nâng cao (throughput), tốc độ (latency) chương trình không bị chậm lại (Context switching)


Dữ liệu không toàn vẹn


Chuyện gì sẽ xảy ra nếu như ta có 1 object Calculation và tạo ra 2 thread, mỗi thread sẽ gọi hàm increase 10 nghìn lần:


class Calculation {

   int x;

   public void increase() {

       x++;

   }

}


Số đúng:      20000

Kết quả chạy: 17309


Cơ chế lock

Synchronized


Là từ khoá modifier cho phép các thread được đồng bộ khi chạy. Với method hoặc block code khi sử dụng từ khoá này thì chỉ 1 thread tại một thời điểm được thực thi.


ReentrantLock


- Cơ chế lock cho phép việc access vào resource bị giới hạn bởi thread nào lấy được lock trước. Các thread sau phải chờ

- ReentrantLock đảm bảo tính công bằng giữa các thread khi đứng trong hàng chờ

- Nguyên tắc: gọi lock trước khi thực hiện và unlock trong finally để giải phóng


lock.lock();

try {

...

} finally {

  lock.unlock();

}


ConcurrentModificationException


private static void addToList() {

   new Thread(() -> {

       for(int i = 0; i < 10000; i++) {

           try {

               list.add(i);

               Thread.sleep(1);

           } catch (InterruptedException e) {

               e.printStackTrace();

           }

       }

   }).start();

}

private static void loopInList() {

   new Thread(() -> {

       try {

           for (Integer i : list) {

               System.out.println(i);

               Thread.sleep(1);

           }

       } catch (InterruptedException e) {

           e.printStackTrace();

       }

   }).start();

}

public static void main(String[] args) throws Exception {

   addToList();

   loopInList();

}


Khi chạy bạn sẽ gặp lỗi sau: java.util.ConcurrentModificationException


Exception in thread "Thread-1" java.util.ConcurrentModificationException

at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)

at java.util.ArrayList$Itr.next(ArrayList.java:859)

at thread.JavaSynchronized.lambda$loopInList$1(JavaSynchronized.java:27)

at java.lang.Thread.run(Thread.java:748)


Lỗi này gặp phải dù chỉ có 1 thread đọc và 1 thread loop qua một list.


Hãy tạo ra một danh sách mới trước khi duyệt phần tử trong list:

 List<Integer> listToLoop = new ArrayList<>(list);



private static void loopInList() {

   new Thread(() -> {

       try {

           List<Integer> listToLoop = new ArrayList<>(list);

           for (Integer i : listToLoop) {

               System.out.println(i);

               Thread.sleep(1);

           }

       } catch (InterruptedException e) {

           e.printStackTrace();

       }

   }).start();

}


Java Collections


- Nếu sử dụng Map hãy lưu ý khi sử dụng Java HashMap vs ConcurrentHashMap

- Nếu sử dụng List hãy xem xét sử dụng:

+ CopyOnWriteArrayList hoặc dùng synchronized khi thay đổi list

+ Sử dụng new ArrayList(list) trong trường hợp duyệt qua mảng mà mảng có thể thay đổi trong thời điểm duyệt.

+ Collections.unmodifiableList(list)


StringBuilder vs StringBuffer

- StringBuilder và StringBuffer giúp cho việc thao tác với xâu (String) trong Java dễ dàng hơn

- StringBuilder là non-threadsafe, StringBuffer là threadsafe

- StringBuilder tốc độ nhanh hơn, StringBuffer tốc độ chậm hơn


SimpleDateFormat vs Thread


SimpleDateFormat là non threadsafe. Ví dụ đoạn code với 2 threads để convert từ kiểu String sang kiểu Datetime:


String dateStr = "2020-08-22T10:00:00";

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");

int n = 100;

for(int t = 0; t < 2; t++) {

   new Thread(() -> {

       try {

           for (int i = 0; i < n; i++) {

               format.parse(dateStr);

           }

       } catch (Exception e) {

           e.printStackTrace();

       }

   }).start();

}


Thread.sleep(5000);


Khi chạy bạn sẽ thấy lỗi:


java.lang.NumberFormatException: For input string: "E.188"

at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)

at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)

at java.lang.Double.parseDouble(Double.java:538)

at java.text.DigitList.getDouble(DigitList.java:169)

at java.text.DecimalFormat.parse(DecimalFormat.java:2089)

at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)

at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)

at java.text.DateFormat.parse(DateFormat.java:364)

at thread.SimpleDateTimeTest.lambda$main$0(SimpleDateTimeTest.java:18)

at java.lang.Thread.run(Thread.java:748)

java.lang.NumberFormatException: For input string: "E.188E1"

at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)

at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)

at java.lang.Double.parseDouble(Double.java:538)

at java.text.DigitList.getDouble(DigitList.java:169)

at java.text.DecimalFormat.parse(DecimalFormat.java:2089)

at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)

at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)

at java.text.DateFormat.parse(DateFormat.java:364)

at thread.SimpleDateTimeTest.lambda$main$0(SimpleDateTimeTest.java:18)

at java.lang.Thread.run(Thread.java:748)


Giải pháp:


- Do vậy với SimpleDateFormat bạn phải dùng cơ chế synchronized hoặc khai báo từng instance mỗi khi sử dụng.


- Đối với việc format sử dụng kiểu dữ liệu LocalDateTime, nên sử dụng class DateTimeFormatter để thay thế vì nó có khả năng threadsafe:

https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html


Mời các bạn xem thêm bài Các cách lập trình đa luồng trong Java - Phần 1.


- Tech Zone -

Thư giãn chút nào!!!

Bài viết liên quan