Java thread-safe example

Ví dụ về thread-safe trong Java , với 1 built-in class khá thường gặp là SimpleDateFormat .

(Ở đây viết dạng chạy với JUnit4 , ai chưa quen có thể copy lại thành hàm main() để chạy)

  @org.junit.Test
  public void testFormattingWithRunnable() {
    // FIXME - dq: To see the bug, use SimpleDateFormat statement
    final java.text.DateFormat df = new java.text.SimpleDateFormat("dd-MMM-yyyy");
    // final DateFormatter df = new vn.ducquoc.util.DateFormatter("dd-MMM-yyyy");
    final String[] testdata = { "01-Jan-1999", "14-Feb-2001", "31-Dec-2007" };
    Runnable runnables[] = new Runnable[testdata.length];
    org.junit.Assert.assertNotNull("Look at the results");
    for (int i = 0; i < runnables.length; i++) {
      final int i2 = i;
      runnables[i] = new Runnable() {
        public void run() {
          try { // 42 here, may adjust 12->120 depending on your computer
            for (int j = 0; j < 42; j++) {
              String str = testdata[i2];
              String str2 = null;
              /* synchronized(df) */{
                Date d = df.parse(str);
                str2 = df.format(d);
              }
              System.out.println("EXPECTED " + str + " ACTUAL " + str2);
            }
          } catch (java.text.ParseException e) {
            throw new RuntimeException("Parse failed");
          }
        }
      };
      new Thread(runnables[i]).start();
    }
  }

 

.

 

Không chỉ Thread/Runnable bị vấn đề mà ngay cả các Callable cũng rứa:

  @org.junit.Test
  public void testParsingWithCallable() throws Exception {

    // FIXME - dq: To see the bug, use SimpleDateFormat statement
    final java.text.DateFormat df = new java.text.SimpleDateFormat("yyyyMMdd");
    // final DateFormatter df = new vn.ducquoc.util.DateFormatter("yyyyMMdd");

    Callable<Date> task = new Callable<Date>() {
      public Date call() throws Exception {
        return df.parse("20101010");
      }
    };

    // pool with 5 threads
    ExecutorService exec = Executors.newFixedThreadPool(5);
    List<Future<Date>> results = new ArrayList<Future<Date>>();

    // perform 42 date conversions, may adjust 12->120 depending on your computer
    for (int i = 0; i < 42; i++) {
      results.add(exec.submit(task));
    }
    exec.shutdown();

    org.junit.Assert.assertNotNull("Look at the results");
    for (Future<Date> result : results) {
      System.out.println(result.get());
    }
  }

 

.

 

 

.

 

Các cách chính để tránh vấn đề cho multi-thread (concurrency) :

1/ dùng từ khóa synchronized của Java cho block hoặc method

 

2/ tạo một đối tượng mới mỗi khi thực hiện tác vụ (bằng cách new , clone() , Class.newInstance() , … )

 

3/ dùng các biến thuộc lớp ThreadLocal của Java

 

.

Theo nguyên lý K.I.S.S/Y.A.G.N.I thì có lẽ nên áp dụng cách 1 .

 

 

UPDATE 2018

Simple runner for unit tests: https://bitbucket.org/ducquoc/jutil-dq/src/master/jutil/src/main/java/vn/ducquoc/jutil/MultiThreadsTestUtil.java

Thêm một số cách intermediate và advanced users (ban đầu không liệt kê vì với đa số trường hợp 3 cách cơ bản ở trên là đủ):

 

4/ dùng thread-safe data structure thay vì sử dụng class trước Java 1.5 (có package  `​java.util.concurrent`,​ `​java.util.concurrent.atomic`, ​java.util.concurrent.locks, …). Ưu tiên JDK, mặc dù vẫn có bên ngoài và thậm chí hiệu năng tốt hơn (VD:  CHashMap )

+ AtomicLong thay vì Long, AtomicInteger thay vì Integer, AtomicLongArray thay vì long[] , LongAdder hoặc LongAccumulator thay vì cộng Long bình thường, etc…

+ ReentrantLock thay vì `​synchronized` keyword.

+ ConcurrentHashMap thay vì HashMap. (đã được viết lại sử dụng AtomicInteger và ReentrantLock internally).

+ CountDownLatch thay vì Integer– hoặc AtomicInteger.decrementAndGet(), …

 

5/ Sử dụng sun.misc.Unsafe , tận dụng khả năng compareAndSwap của nó (native method). Các ThreadLocal và AtomicXyz cũng sử dụng Unsafe internally.

 

6/ Sử dụng more libraries (ForkJoinPool Java7+ , NonBlockingHashMap, HPC, Trove, …) , thậm chí viết lại phiên bản riêng custom để gỉai quyết concurrency/parallelism cho 1 vấn đề cụ thể.

 

 

./.

 

About DucQuoc.wordpress.com

A coder, content creator, and a proud father of 2 princesses.
This entry was posted in Coding, Marketing. Bookmark the permalink.

5 Responses to Java thread-safe example

  1. Actually, there is one or more other approaches, which are the ‘lower level’ of above approaches. For example, instead of ‘synchronized’ block we can use Lock of java.util.concurrent, such as ReentrantLock. Or use some AtomicInteger, AtomicReference instead of ThreadLocal. Or use ‘partial instances’ by getDateInstance() with some cache like ConcurrentHashMap (and maybe SoftReference/WeakReference)

    These ‘low-level’ approach is suitable when you want to know more about underlying techniques to handle the multi-thread, like mutex, semaphore, blockingQueue, nonBlocking algorithms… or when it (really) reaches the phase of performance optimization . The most use case is, to impress some guys who don’t even know “synchronized”, such as a non-technical manager :- ) .

  2. Pingback: Logging best practices | DucQuoc's Blog

  3. Pingback: Sprint end DoW | DucQuoc's Blog

  4. Pingback: Sprint end DoW | DucQuoc's Blog

  5. As of Java 8+ (i.e. JDK/JRE version 8 and later), we should use DateTimeFormatter to format/parse to `LocalDateTime/LocalDate`, instead of `SimpleDateFormat` to Date.

    DateTimeFormatter.ofPattern(“yyyy-MM-dd HH:mm:ss”);

    Sample test class:
    https://bitbucket.org/ducquoc/jutil-dq/src/HEAD/cassandra-dq/src/test/java/vn/ducquoc/util/DateFormatterTest.java

    DateTimeFormatter and java.time.* package in general was inspired from “joda time” library, which is considered as “ported” from Joda time in to JDK as of Java 8. It has better API (fluent interface, intuitive, thread-safe, etc…) compared to old SimpleDateFormat.

Leave a comment