Java中SimpleDateFormat 線程安全

SimpleDateFormat 是一個非線程安全的類,若是在多線程下做爲變量使用,須要特別注意線程安全問題,否則會出現莫名其妙的問題 ##多線程下問題重現html

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestSimpleDateFormat implements Runnable{
	
	private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
	
	private String date;
	public TestSimpleDateFormat(String date){
		this.date = date;
	}	
	public static void main(String[] args) {        
     ExecutorService  es = Executors.newFixedThreadPool(2);
     es.execute(new TestSimpleDateFormat("2017-04-10"));
     es.execute(new TestSimpleDateFormat("2017-05-10"));		
	}
	@Override
	public void run() {
		Date now = null;
		try {
			now = sdf.parse(this.date);
		} catch (ParseException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getId()+"格式化日期"+sdf.format(now));		
	}
}

輸出的結果可能各不相同java

13格式化日期2200-05-10
14格式化日期2200-05-10
Exception in thread "pool-1-thread-2" java.lang.NumberFormatException: multiple points
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
	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:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at TestSimpleDateFormat.run(TestSimpleDateFormat.java:27)
	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)

##出現這種問題的緣由 Synchronization Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.git

SimpleDateFormat並不是是同步的,建議每一個線程建立單獨的實例。在多線程併發訪問format實例的時候,必須加鎖。 ##formatapi

// Called from Format after creating a FieldDelegate
   private StringBuffer format(Date date, StringBuffer toAppendTo,     FieldDelegate delegate) {
      ..........
       // Convert input date to time field list
       calendar.setTime(date);
       .........
   }

##parse安全

Date parsedDate;
        try {
            parsedDate = calb.establish(calendar).getTime();
            // If the year value is ambiguous,
            // then the two-digit year == the default start year
            if (ambiguousYear[0]) {
                if (parsedDate.before(defaultCenturyStart)) {
                    parsedDate = calb.addYear(100).establish(calendar).getTime();
                }
            }
        }
Calendar establish(Calendar cal) {
       ...........      
        cal.clear();
      ..........
 }
SimpleDateFormat繼承自DateFormat,DateFormat內有一個屬性字段Calendar實例。若是在多線程環境下使用,每一個線程會共享這個實例.
 假設線程A調用parse()方法,並進行了calendar.clear()後還未執行calendar.getTime()的時候,線程B又調用parse(),這時候線程B也執行了sdf.clear()方法,這樣就會致使線程A的calendar數據被清空或者A執行calendar.clear()後被掛起,這是B開始調用sdf.parse()並順利結束,這樣A的calendar內存儲的date變成了B設置的calendar的date

##解決方法 ###1.使用ThreadLocal 每一個線程使用本身線程獨立的simpledateformat實例多線程

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestSimpleDateFormat implements Runnable{
	
   private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>() {  
       protected synchronized SimpleDateFormat initialValue() {  
           return  new SimpleDateFormat("yyyy-MM-dd");  
       }  
   };  
	private String date;
	public TestSimpleDateFormat(String date){
		this.date = date;
	}	
	public static void main(String[] args) {        
       ExecutorService  es = Executors.newFixedThreadPool(2);
       es.execute(new TestSimpleDateFormat("2017-04-10"));
       es.execute(new TestSimpleDateFormat("2017-05-10"));		
	}
	@Override
	public void run() {
		SimpleDateFormat sdf = threadLocal.get();
		Date now = null;
		try {
			now = sdf.parse(this.date);
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getId()+"格式化日期"+sdf.format(now));		
	}
}
13格式化日期2017-04-10
14格式化日期2017-05-10

###2.多線程下每一次都從新new出一個新對象併發

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestSimpleDateFormat implements Runnable{
	

	private String date;
	public TestSimpleDateFormat(String date){
		this.date = date;
	}
	
	public static void main(String[] args) {        
        ExecutorService  es = Executors.newFixedThreadPool(2);
        es.execute(new TestSimpleDateFormat("2017-04-10"));
        es.execute(new TestSimpleDateFormat("2017-05-10"));		
	}
	@Override
	public void run() {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		Date now = null;
		try {
			now = sdf.parse(this.date);
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getId()+"格式化日期"+sdf.format(now));		
	}
}

###3.使用時對SimpleDateFormat加鎖oracle

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestSimpleDateFormat implements Runnable{
	
	private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
	
	private String date;
	public TestSimpleDateFormat(String date){
		this.date = date;
	}
	
	public static void main(String[] args) {        
        ExecutorService  es = Executors.newFixedThreadPool(2);
        es.execute(new TestSimpleDateFormat("2017-04-10"));
        es.execute(new TestSimpleDateFormat("2017-05-10"));		
	}
	@Override
	public void run() {
		synchronized (sdf) {
			Date now = null;
			try {
				now = sdf.parse(this.date);
			} catch (ParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getId()+"格式化日期"+sdf.format(now));
		}
				
	}
}
相關文章
相關標籤/搜索