項目常用包系列--SingleDateFormat

其他網址

SimpleDateFormat線程不安全原因及解決方案 - 楊七 - 博客園
SimpleDateFormat線程不安全及解決辦法_java_誠-CSDN博客

線程不安全實例

package org.example.a;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;

public class Demo{
    public static void main(String[] args) {
        final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Callable<Date> task = new Callable<Date>() {
            public Date call() throws Exception {
                return dateFormat.parse("2016-12-18 15:00:34");
            }
        };

        // 創建5個線程的線程池
        ExecutorService exec = Executors.newFixedThreadPool(5);
        List<Future<Date>> results = new ArrayList<Future<Date>>();
        for (int i = 0; i < 10; i++) {
            results.add(exec.submit(task));
        }
        exec.shutdown();
        // 輸出結果
        for (Future<Date> result : results) {
            try {
                System.out.println(result.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
}

執行結果(出現異常)

Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
java.util.concurrent.ExecutionException: java.lang.NumberFormatException: For input string: "E34"
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:192)
    at org.example.a.Demo.main(Demo.java:29)
Caused by: java.lang.NumberFormatException: For input string: "E34"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Long.parseLong(Long.java:589)
    at java.lang.Long.parseLong(Long.java:631)
    at java.text.DigitList.getLong(DigitList.java:195)
    at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
    at java.text.DateFormat.parse(DateFormat.java:364)
    at org.example.a.Demo$1.call(Demo.java:15)
    at org.example.a.Demo$1.call(Demo.java:13)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
java.util.concurrent.ExecutionException: java.lang.NumberFormatException: For input string: "E342"
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:192)
Sun Dec 18 15:00:34 CST 2016    at org.example.a.Demo.main(Demo.java:29)
Caused by: java.lang.NumberFormatException: For input string: "E342"
    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:2056)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
    at java.text.DateFormat.parse(DateFormat.java:364)
    at org.example.a.Demo$1.call(Demo.java:15)
    at org.example.a.Demo$1.call(Demo.java:13)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

java.util.concurrent.ExecutionException: java.lang.NumberFormatException: For input string: ""
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:192)
    at org.example.a.Demo.main(Demo.java:29)
Caused by: java.lang.NumberFormatException: For input string: ""
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Long.parseLong(Long.java:601)
    at java.lang.Long.parseLong(Long.java:631)
    at java.text.DigitList.getLong(DigitList.java:195)
    at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
    at java.text.DateFormat.parse(DateFormat.java:364)
    at org.example.a.Demo$1.call(Demo.java:15)
    at org.example.a.Demo$1.call(Demo.java:13)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016 

線程不安全原因

SimpleDateFormat線程不安全及解決辦法_timmyroy的博客-CSDN博客

        SimpleDateFormat類內部有一個Calendar對象引用,它用來儲存和這個SimpleDateFormat相關的日期信息,例如sdf.parse(dateStr),sdf.format(date) 諸如此類的方法參數傳入的日期相關String,Date等等, 都是交由Calendar引用來儲存的.這樣就會導致一個問題:如果你的SimpleDateFormat是多個thread 之間共享的, 那麼也共享這個Calendar引用。假定線程A和線程B都進入了parse(text, pos) 方法, 線程B執行到calendar.clear()後,線程A執行到calendar.getTime(), 那麼就會有問題。

線程安全的方案

方案1:新建局部變量(開銷大)

缺點:每調用一次方法就會創建一個SimpleDateFormat對象,方法結束又要作爲垃圾回收。

package org.example.a;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;

public class Demo{
    public static void main(String[] args) {
        Callable<Date> task = new Callable<Date>() {
            public Date call() throws Exception {
                DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                return dateFormat.parse("2016-12-18 15:00:34");
            }
        };

        // 創建5個線程的線程池
        ExecutorService exec = Executors.newFixedThreadPool(5);
        List<Future<Date>> results = new ArrayList<Future<Date>>();
        for (int i = 0; i < 10; i++) {
            results.add(exec.submit(task));
        }
        exec.shutdown();
        // 輸出結果
        for (Future<Date> result : results) {
            try {
                System.out.println(result.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
}

執行結果(正常)

Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016 

方案2:方法加synchronized(性能差)

 

package org.example.a;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;

public class Demo{
    public static void main(String[] args) {
        final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Callable<Date> task = new Callable<Date>() {
            public Date call() throws Exception {
                synchronized (dateFormat) {
                    return dateFormat.parse("2016-12-18 15:00:34");
                }
            }
        };

        // 創建5個線程的線程池
        ExecutorService exec = Executors.newFixedThreadPool(5);
        List<Future<Date>> results = new ArrayList<Future<Date>>();
        for (int i = 0; i < 10; i++) {
            results.add(exec.submit(task));
        }
        exec.shutdown();
        // 輸出結果
        for (Future<Date> result : results) {
            try {
                System.out.println(result.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
}

執行結果(正常)

Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016

方案3:使用ThreadLocal(推薦) 

package org.example.a;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;

class DateFormatThreadSafe {
    public static final ThreadLocal<DateFormat> THREAD_LOCAL = new ThreadLocal<DateFormat>() {
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };
}

public class Demo{
    public static void main(String[] args) {
        Callable<Date> task = new Callable<Date>() {
            public Date call() throws Exception {
                DateFormat dateFormat = DateFormatThreadSafe.THREAD_LOCAL.get();
                return dateFormat.parse("2016-12-18 15:00:34");
            }
        };

        // 創建5個線程的線程池
        ExecutorService exec = Executors.newFixedThreadPool(5);
        List<Future<Date>> results = new ArrayList<Future<Date>>();
        for (int i = 0; i < 10; i++) {
            results.add(exec.submit(task));
        }
        exec.shutdown();
        // 輸出結果
        for (Future<Date> result : results) {
            try {
                System.out.println(result.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
}

執行結果(正常)

Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016
Sun Dec 18 15:00:34 CST 2016

方案4: 針對JDK1.8

        如果是JDK8的應用,可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替Simpledateformatter,官方給出的解釋:simple beautiful strong immutable thread-safe。

package org.example.a;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class Demo{
    public static void main(String[] args) {
        DateTimeFormatter dtf=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        Callable<LocalDateTime> task = new Callable<LocalDateTime>() {
            public LocalDateTime call() throws Exception {
                return LocalDateTime.parse("2016-12-18 15:00:34", dtf);
            }
        };

        // 創建5個線程的線程池
        ExecutorService exec = Executors.newFixedThreadPool(5);
        List<Future<LocalDateTime>> results = new ArrayList<Future<LocalDateTime>>();
        for (int i = 0; i < 10; i++) {
            results.add(exec.submit(task));
        }
        exec.shutdown();
        // 輸出結果
        for (Future<LocalDateTime> result : results) {
            try {
                System.out.println(result.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
}

執行結果(正常)

2016-12-18T15:00:34
2016-12-18T15:00:34
2016-12-18T15:00:34
2016-12-18T15:00:34
2016-12-18T15:00:34
2016-12-18T15:00:34
2016-12-18T15:00:34
2016-12-18T15:00:34
2016-12-18T15:00:34
2016-12-18T15:00:34

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章