SimpleDateFormat与DateTimeFormatter的线程安全性

简述: SimpleDateFormat线程不安全,DateTimeFormatter线程安全.

SimpleDateFormat

代码分析:

    /**
     * The {@link Calendar} instance used for calculating the date-time fields
     * and the instant of time. This field is used for both formatting and
     * parsing.
     *
     * <p>Subclasses should initialize this field to a {@link Calendar}
     * appropriate for the {@link Locale} associated with this
     * <code>DateFormat</code>.
     * @serial
     */
    protected Calendar calendar;
    
    // 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);

        boolean useDateFormatSymbols = useDateFormatSymbols();

        for (int i = 0; i < compiledPattern.length; ) {
            int tag = compiledPattern[i] >>> 8;
            int count = compiledPattern[i++] & 0xff;
            if (count == 255) {
                count = compiledPattern[i++] << 16;
                count |= compiledPattern[i++];
            }

            switch (tag) {
            case TAG_QUOTE_ASCII_CHAR:
                toAppendTo.append((char)count);
                break;

            case TAG_QUOTE_CHARS:
                toAppendTo.append(compiledPattern, i, count);
                i += count;
                break;

            default:
                subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
                break;
            }
        }
        return toAppendTo;
    }

SimpleDateFormat在format方法中将入参日期对象的时间set到calendar中calendar.setTime(date),calendar是全局变量,在SimpleDateFormat的多个方法中用到,一旦出现多线程调用的情况,calendar的值就会被修改,导致结果不正确甚至发生报错,所以是线程不安全的.

例如:

import org.junit.Test;

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

public class DateTimeFormatterTest {
	
	public SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
	
	@Test
	public void testUnsafeSimpleDateFormat() {
		ExecutorService threadPool = Executors.newFixedThreadPool(10);
		while (true) {
			threadPool.execute(() -> {
				System.out.println("日期范围: "+getDaysBetween("2020-11-22"));
			});
		}
	}
	
	public List<String> getDaysBetween(String start) {
		List<String> dates = new ArrayList<>();
		try {
			Date date = sdf.parse(start);
			Calendar instance = Calendar.getInstance();
			Calendar now = Calendar.getInstance();
			instance.setTime(date);
			while (instance.before(now)) {
				dates.add(sdf.format(instance.getTime()));
				instance.add(Calendar.DAY_OF_MONTH, 1);
			}
		} catch (ParseException e) {
			//
		}
		return dates;
	}
}

在这里插入图片描述

DateTimeFormatter

ISO_LOCAL_DATE(即’1970-01-01’格式)为例
实例化过程:

    public static final DateTimeFormatter ISO_LOCAL_DATE;
    static {
        ISO_LOCAL_DATE = new DateTimeFormatterBuilder()
                .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
                .appendLiteral('-')
                .appendValue(MONTH_OF_YEAR, 2)
                .appendLiteral('-')
                .appendValue(DAY_OF_MONTH, 2)
                .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
    }
  • appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD): 新建一个NumberPrinterParser对象(解析/输出YEAR)并保存到printerParsers集合中
  • appendLiteral(’-’): 新建一个CharLiteralPrinterParser对象并保存到printerParsers集合中
  • appendValue(MONTH_OF_YEAR, 2): 新建一个NumberPrinterParser对象(解析/输出MONTH_OF_YEAR)并保存到printerParsers集合中
  • appendLiteral(’-’): 新建一个CharLiteralPrinterParser对象并保存到printerParsers集合中
  • appendValue(DAY_OF_MONTH, 2): 新建一个NumberPrinterParser对象(解析/输出DAY_OF_MONTH)并保存到printerParsers集合中
  • toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE): 新建一个DateTimeFormatter对象,如下:
    private DateTimeFormatter toFormatter(Locale locale, ResolverStyle resolverStyle, Chronology chrono) {
        Objects.requireNonNull(locale, "locale");
        while (active.parent != null) {
            optionalEnd();
        }
        CompositePrinterParser pp = new CompositePrinterParser(printerParsers, false);
        return new DateTimeFormatter(pp, locale, DecimalStyle.STANDARD,
                resolverStyle, null, chrono, null);
    }

对LocalDate做格式化处理时,LocalDate#format:

    @Override  // override for Javadoc and performance
    public String format(DateTimeFormatter formatter) {
        Objects.requireNonNull(formatter, "formatter");
        return formatter.format(this);
    }

查看DateTimeFormatter#format:

    public String format(TemporalAccessor temporal) {
        StringBuilder buf = new StringBuilder(32);
        formatTo(temporal, buf);
        return buf.toString();
    }

    public void formatTo(TemporalAccessor temporal, Appendable appendable) {
        Objects.requireNonNull(temporal, "temporal");
        Objects.requireNonNull(appendable, "appendable");
        try {
            DateTimePrintContext context = new DateTimePrintContext(temporal, this);
            if (appendable instanceof StringBuilder) {
                printerParser.format(context, (StringBuilder) appendable);
            } else {
                // buffer output to avoid writing to appendable in case of error
                StringBuilder buf = new StringBuilder(32);
                printerParser.format(context, buf);
                appendable.append(buf);
            }
        } catch (IOException ex) {
            throw new DateTimeException(ex.getMessage(), ex);
        }
    }

从代码可见, LocalDate对象被封装在一个新建的DateTimePrintContext对象中,作为入参传给printerParser对象的format方法,此时不会有线程安全问题.
printerParser就是前面toFormatter方法中的new CompositePrinterParser对象, 再点进printerParser.format方法,

        @Override
        public boolean format(DateTimePrintContext context, StringBuilder buf) {
            int length = buf.length();
            if (optional) {
                context.startOptional();
            }
            try {
                for (DateTimePrinterParser pp : printerParsers) {
                    if (pp.format(context, buf) == false) {
                        buf.setLength(length);  // reset buffer
                        return true;
                    }
                }
            } finally {
                if (optional) {
                    context.endOptional();
                }
            }
            return true;
        }

可以看到,里面是最初保存在printerParsers集合中的那5个DateTimePrinterParser对象在循环调用各自的format方法做格式化处理,并将结果拼接在StringBuilder对象中.

以上分析可知,DateTimeFormatter在格式化日期时, 日期对象和StringBuilder对象自始至终都是作为局部变量传入到方法中,而不是像SimpleDateFormat那样把日期对象设置到全局变量calendar中进行处理,从而避免了线程安全的问题.

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值