Call to method of static java.text.DateFormat

| Java | 657 | 484 วันที่แล้ว
วันนี้ได้ลอง Scan Code โดยใช้ FindBugs ดูครับ ปรากฎว่ามีโค้ดอยู่ส่วนหนึ่งซึ่งอยู่ในหมวด Muitithreaded correctness ซึ่งโค้ดที่ได้เขียนขึ้น มีตัวอย่างตามด้านล่าง

package com.howtoclicks.utils;

import org.junit.Test;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class DateTimeUtils {
    public static final String DATE_FORMAT = "dd/mm/yyyy";
    private static final DateFormat df = new SimpleDateFormat(DATE_FORMAT);

    @Test
    public void test(){
        Calendar calendar = Calendar.getInstance();
        String str = conventToString(calendar.getTime());

        System.out.println("How To Clicks Test : " + str);
    }

    public static String conventToString(Date date) {
        return df.format(date);
    }

    public static Date conventToDate(String dateStr) throws Exception {
        return df.parse(dateStr);
    }
}
ซึ่งผลที่ Scan ที่ได้ก็จะปรากฎประมาณนี้คับ
เมื่อดูรายละเอียดแล้วก็จะพบประมาณว่า
Call to method of static java.text.DateFormat

Class:
DateTimeUtils (com.howtoclicks.utils) line 23

Method:
conventToString (com.howtoclicks.utils.DateTimeUtils.conventToString(Date))

Field:
df

Priority:
     Medium Confidence Multithreaded correctness
 
Problem classification:
Multithreaded correctness (Static use of type Calendar or DateFormat)
STCAL_INVOKE_ON_STATIC_DATE_FORMAT_INSTANCE (Call to static DateFormat)

Notes:
Field com.howtoclicks.utils.DateTimeUtils.df
Called method java.text.SimpleDateFormat.format(Date)
StaticCalendarDetector (STCAL)
Call to static DateFormat As the JavaDoc states, DateFormats are inherently unsafe for multithreaded use. The detector has found a call to an instance of DateFormat that has been obtained via a static field. This looks suspicous. For more information on this see Sun Bug #6231579 and Sun Bug #6178997 ถ้าอ่านดูก็จะรู้ว่า DateFormats นั้น unsafe for thread นั่นคือ DateFormats มันไม่ซับพอร์ตสำหรับหลาย ๆ thread นั่นเอง ถ้าเกิดมีการเขียนและอ่านคนละ thread พร้อม ๆ กันก็จะเกิด Exception นั่นเอง ดังนั้น เราควรแก้ แล้วจะแก้ยังไงดี?? ทำยังไงดีละให้ DateFormats มันใช้หลาย ๆ Thread พร้อม ๆ กันได้ เราก็ new ใหม่สิเพื่อจะไม่เกิด Exception ดังนั้น โค้ดใหม่ที่ได้ก็จะเป็น
package com.howtoclicks.utils;

import org.junit.Test;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class DateTimeUtils {
    public static final String DATE_FORMAT = "dd/mm/yyyy";
//    private static final DateFormat df = new SimpleDateFormat(DATE_FORMAT);

    @Test
    public void test(){
        Calendar calendar = Calendar.getInstance();
        String str = conventToString(calendar.getTime());

        System.out.println("How To Clicks Test : " + str);
    }

    public static String conventToString(Date date) {
        DateFormat df = new SimpleDateFormat(DATE_FORMAT);
        return df.format(date);
    }

    public static Date conventToDate(String dateStr) throws Exception {
        DateFormat df = new SimpleDateFormat(DATE_FORMAT);
        return df.parse(dateStr);
    }
}
จากโค้ด เราได้ทำการ new DateFormat ใหม่ทุกครั้งที่มีการเรียก method ดังนั้นก็จะไม่เกิดการเรียกใช้ DateFormat คนละ Thread แล้ว เมื่อลองรัน FindBugs ดูแล้วก็จะไม่พบการแจ้งเตือน แต่!! ถ้าเกิดว่ามีการเรียกซ้ำ ๆ บ่อย ๆ จาก Thread เดียวกัน ก็จะทำให้เกิด Object ขึ้นมาแบบสิ้นเปลืองอ่ะดิ เอางี้ดีม่ะ new เป็น ThreadLocal ดังนั้นโค้ดที่ได้ใหม่ก็จะได้เป็น ThreadLocal ตามตัวอย่างโค้ดด้านล่าง
package com.howtoclicks.utils;

import org.junit.Test;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class DateTimeUtils {
    public static final String DATE_FORMAT = "dd/mm/yyyy";
    private static final ThreadLocal df = new ThreadLocal(){
        @Override
        protected SimpleDateFormat initialValue()
        {
            return new SimpleDateFormat(DATE_FORMAT);
        }
    };

    @Test
    public void test(){
        Calendar calendar = Calendar.getInstance();
        String str = conventToString(calendar.getTime());

        System.out.println("How To Clicks Test : " + str);
    }

    public static String conventToString(Date date) {
        return df.get().format(date);
    }

    public static Date conventToDate(String dateStr) throws Exception {
        return df.get().parse(dateStr);
    }
}
แต่ใน java 8 เราสามารถใช้ ThreadLocal.withInitial ได้เป็นการ Initial ดังนั้น เมื่อใช้ java 8 ก็จะได้โค้ดแบบนี้
package com.howtoclicks.utils;

import org.junit.Test;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class DateTimeUtils {
    public static final String DATE_FORMAT = "dd/mm/yyyy";
    private static final ThreadLocal df = ThreadLocal.withInitial(() -> new SimpleDateFormat(DATE_FORMAT));

    @Test
    public void test(){
        Calendar calendar = Calendar.getInstance();
        String str = conventToString(calendar.getTime());

        System.out.println("How To Clicks Test : " + str);
    }

    public static String conventToString(Date date) {
        return df.get().format(date);
    }

    public static Date conventToDate(String dateStr) throws Exception {
        return df.get().parse(dateStr);
    }
}
จะเห็นว่า เราจะใช้ ThreadLocal.withInitial แล้วใช้ lampda expression เพื่อ return เป็น new SimpleDateFormat เพียงแค่นี้ เวลามี Thread ใหม่เข้ามา จึงจะสร้าง object แต่ถ้า Thread เดิมเรียกใช้ Method ก็จะเป็นการเรียกใช้ Object ตัวเดิม
comments

[1]
Adobe-PDF
700 D
[1]
Android
591 D
[40]
Animal
684 D
[1]
Apache
700 D
[2]
[10]
[2]
CMS-SMF
701 D
[1]
[3]
[1]
Database
700 D
[4]
[1]
Docker
595 D
[1]
Fruit
684 D
[2]
Git
487 D
[5]
HTML
487 D
[1]
Housework
689 D
[2]
IT
681 D
[2]
Imacro
701 D
[17]
Java
484 D
[1]
Java-Web
591 D
[1]
[2]
MQL5
513 D
[3]
MakeMoney
486 D
[18]
[1]
Maven
485 D
[1]
Mobile
698 D
[1]
NodeJs
486 D
[3]
Physics
482 D
[4]
PugJS
486 D
[2]
React
496 D
[132]
Science
683 D
[1]
[2]
Spring
485 D
[7]
[2]
[1]
[4]
Ubuntu
650 D
[1]
WebLogic
682 D
[4]