Call to method of static java.text.DateFormat

| Java | 1383 | 947 วันที่แล้ว
วันนี้ได้ลอง 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]
AGI
58 D
[1]
Adobe-PDF
1162 D
[1]
Android
1053 D
[2]
Angular
36 D
[40]
Animal
1146 D
[1]
Apache
1162 D
[1]
[4]
[2]
[1]
[10]
CMS-Joomla
1163 D
[2]
CMS-SMF
1163 D
[1]
[4]
[1]
[3]
[1]
Database
1162 D
[4]
[1]
DeAI
23 D
[1]
DeData
23 D
[1]
DeFi
23 D
[2]
Docker
53 D
[2]
[1]
Forex
20 D
[1]
Fruit
1147 D
[1]
General
44 D
[2]
Git
949 D
[6]
HTML
53 D
[1]
Health
48 D
[1]
Housework
1151 D
[2]
IT
1143 D
[2]
Imacro
1163 D
[20]
Java
25 D
[1]
Java-Web
1053 D
[2]
[2]
MQL5
975 D
[3]
MakeMoney
948 D
[18]
[1]
[1]
Maven
947 D
[5]
[1]
Mobile
1160 D
[2]
NGINX
36 D
[2]
NodeJs
53 D
[1]
Oracle
53 D
[3]
Physics
944 D
[4]
PugJS
949 D
[2]
React
958 D
[132]
Science
1145 D
[2]
[5]
Spring
36 D
[12]
[7]
[2]
[1]
[4]
Ubuntu
1113 D
[1]
WebLogic
1144 D
[4]