Java中SimpleDateFormat 线程安全

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 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));		
	}
}

输出的结果可能各不相同

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.

SimpleDateFormat并非是同步的,建议每个线程创建单独的实例。在多线程并发访问format实例的时候,必须加锁。 ##format

 // 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加锁

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));
		}
				
	}
}

转载于:https://my.oschina.net/u/1028135/blog/897012

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值