回顾一下以前的那套日期时间API,你就能发现它是线程不安全的,是可变的。这里就以传统日期时间格式化为例,看看它存在什么多线程安全问题?

如果我们想要使用SimpleDateFormat类来对一个时间或者日期进行格式化,并且还要使用多线程来操作,即使用多线程同时对一个时间或者日期进行格式化,那么该咋办呢?我们可以创建一个线程池,然后分10次去访问定义好的一个任务(该任务就是专门用于格式化一个时间或者日期的),都来解析某个时间或者日期。

package com.meimeixia.java8;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * 这里就以传统日期时间格式化为例,看看它存在什么多线程安全问题?
 * @author liayun
 *
 */
public class TestSimpleDateFormat {
	
    public static void main(String[] args) throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        
        //定义好如下一个任务(task),该任务就是专门用于格式化一个时间或者日期的
        Callable<Date> task = new Callable<Date>() {

            @Override
            public Date call() throws Exception {
                return sdf.parse("20191207");
            }
            
        };
        
        //创建一个长度为10的线程池
        ExecutorService pool = Executors.newFixedThreadPool(10);
        
        List<Future<Date>> results = new ArrayList<Future<Date>>();
        
        //分10次去访问以上定义好的任务(task),然后它就会返回一个结果(叫Future),结果我都给它放在上面的集合里面
        for (int i = 0; i < 10; i++) {
            results.add(pool.submit(task));
        }
        
        for (Future<Date> future : results) {
            System.out.println(future.get());
        }
    }

}

运行以上程序,会发现报了如下错误,日期已经格式化不下去了。这说明已经存在多线程安全问题了,也就是说SimpleDateFormat类或者传统的时间日期API均存在多线程安全问题。

Pattern java 是线程安全的吗_安全问题


抛开Java 8来说的话,要是以前,这个问题该怎么解决呢?要想解决这个多线程安全问题,就得上锁。这就引出了一系列的问题,对什么上锁?怎么上锁?上什么锁?一句话搞定,使用ThreadLocal类对以上程序中的sdf变量上锁,说白了,ThreadLocal类就可以锁这个变量。那怎么怎么上锁呢?

package com.meimeixia.java8;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateFormatThreadLocal {
	
	/*
	 * ThreadLocal说白了,是不是就可以锁这个变量啊?
	 */
	private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
		//ThreadLocal里面有一个供子类继承的方法,即initialValue()
		protected DateFormat initialValue() {
			return new SimpleDateFormat("yyyyMMdd");
		}
	};
	
	public static Date convert(String source) throws ParseException {
		return df.get().parse(source);
	}

}

然后,使用被ThreadLocal锁上的SimpleDateFormat来格式化一个时间或者日期。

package com.meimeixia.java8;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * 这里就以传统日期时间格式化为例,看看它存在什么多线程安全问题?
 * @author liayun
 *
 */
public class TestSimpleDateFormat {
	
	public static void main(String[] args) throws Exception {
		//定义好如下一个任务(task),该任务就是专门用于格式化一个时间或者日期的
		Callable<Date> task = new Callable<Date>() {

			@Override
			public Date call() throws Exception {
				//这次的SimpleDateFormat是不是被ThreadLocal给锁上了啊!
				return DateFormatThreadLocal.convert("20191205");
			}
			
		};
		
		//创建一个长度为10的线程池
		ExecutorService pool = Executors.newFixedThreadPool(10);
		
		List<Future<Date>> results = new ArrayList<Future<Date>>();
		
		//分10次去访问以上定义好的任务(task),然后它就会返回一个结果(叫Future),结果我都给它放在上面的集合里面
		for (int i = 0; i < 10; i++) {
			results.add(pool.submit(task));
		}
		
		for (Future<Date> future : results) {
			System.out.println(future.get());
		}
		
		//运行以上程序,发现程序没停下来!为什么啊?池忘记关了!
		pool.shutdown();
	}
	
}

此时,运行以上程序,你便能在Eclipse控制台看到如下打印内容了。

Pattern java 是线程安全的吗_java_02


这说明通过上锁解决了传统日期时间格式化的多线程安全问题。

而现在,使用Java 8中这套全新的日期时间API之后,就没有什么多线程安全问题了,因为你不管做什么样的改变,它都会给你产生一个全新的实例,所以说它是线程安全的,这样就解决了多线程的安全问题。

package com.meimeixia.java8;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * 这里就以传统日期时间格式化为例,看看它存在什么多线程安全问题?
 * @author liayun
 *
 */
public class TestSimpleDateFormat {
	
	public static void main(String[] args) throws Exception {              
		// DateTimeFormatter dtf = DateTimeFormatter.ISO_LOCAL_DATE;//常用的格式
		DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");//指定自己指定的格式
		
		//现在,Java 8以后,这套全新的日期时间API(例如LocalDate、LocalTime、LocalDateTime类)中类的实例完全就是不可变的对象,这就解决了多线程安全问题
		Callable<LocalDate> task = new Callable<LocalDate>() {

			@Override
			public LocalDate call() throws Exception {
				//使用Java 8中这套全新的日期时间API之后,就没有什么多线程安全问题了,因为你不管做什么样的改变,它都会给你产生一个全新的实例,所以说它是线程安全的,这样就解决了多线程的安全问题。
				return LocalDate.parse("20191203", dtf);
			}
			
		};
		
		//创建一个长度为10的线程池
		ExecutorService pool = Executors.newFixedThreadPool(10);
		
		List<Future<LocalDate>> results = new ArrayList<Future<LocalDate>>();
		
		//分10次去访问以上定义好的任务(task),然后它就会返回一个结果(叫Future),结果我都给它放在上面的集合里面
		for (int i = 0; i < 10; i++) {
			results.add(pool.submit(task));
		}
		
		for (Future<LocalDate> future : results) {
			System.out.println(future.get());
		}
		
		//运行以上程序,发现程序没停下来!为什么啊?池忘记关了!
		pool.shutdown();
	}
	
}

此时,运行以上程序,你便能在Eclipse控制台看到如下打印内容了。

Pattern java 是线程安全的吗_安全问题_03


这就说明了Java 8中这套全新的日期时间API是线程安全的。