Call to method of static java.text.DateFormat

| Java | 1189 | 838 วันที่แล้ว
วันนี้ได้ลอง 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
1053 D
[1]
Android
944 D
[40]
Animal
1037 D
[1]
Apache
1053 D
[2]
[1]
[10]
CMS-Joomla
1054 D
[2]
CMS-SMF
1054 D
[1]
[3]
[1]
Database
1053 D
[4]
[1]
Docker
949 D
[1]
Fruit
1038 D
[2]
Git
840 D
[5]
HTML
840 D
[1]
Housework
1042 D
[2]
IT
1034 D
[2]
Imacro
1054 D
[17]
Java
838 D
[1]
Java-Web
944 D
[1]
[2]
MQL5
866 D
[3]
MakeMoney
839 D
[18]
[1]
[1]
Maven
838 D
[5]
[1]
Mobile
1051 D
[1]
NodeJs
840 D
[3]
Physics
835 D
[4]
PugJS
840 D
[2]
React
849 D
[132]
Science
1036 D
[1]
[2]
Spring
838 D
[12]
[7]
[2]
[1]
[4]
Ubuntu
1004 D
[1]
WebLogic
1035 D
[4]