本文转载自微信公众号“Java中文社区”,作者雷哥。转载本文请联系Java中文社区公众号。1、什么是线程不安全?线程不安全也称为非线程安全。意思是在多线程执行中,如果程序的执行结果与预期的结果不匹配,就称为线程不安全。线程不安全代码SimpleDateFormat是线程不安全的典型例子。接下来,让我们来实现它。首先,我们创建10个线程来格式化时间。每次格式化要格式化的时间都不一样,所以如果程序正确执行,会打印出10个不同的值。接下来我们看一下具体的代码。实现:importjava.text.SimpleDateFormat;importjava.util.Date;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;publicclassSimpleDateFormatExample{//创建SimpleDateFormat对象privatestaticSimpleDateFormatsimpleDateFormat=newSimpleDateFormat("mm:mainss");publicstaticvoidString[]args){//创建线程池ExecutorServicethreadPool=Executors.newFixedThreadPool(10);//执行10次格式化for(inti=0;i<10;i++){intfinalI=i;//线程池执行任务threadPool.execute(newRunnable(){@Overridepublicvoidrun(){//创建时间对象Datedate=newDate(finalI*1000);//执行时间格式并打印结果System.out.println(simpleDateFormat.format(date));}});}}}我们期望的正确结果是这样的(打印10次的值都不一样):然而上面程序的运行结果如下:从上面的结果可以看出当使用SimpleDateFormat的多线程时间格式化不是线程安全的。2、解决方案SimpleDateFormat线程不安全的解决方案一共包括以下5种:将SimpleDateFormat定义为局部变量;使用同步锁执行;使用Lock锁执行(类似方案二);使用线程本地;使用JDK8提供的DateTimeFormat。下面我们分别来看一下各个方案的具体实现。①将SimpleDateFormat改为局部变量将SimpleDateFormat定义为局部变量时,由于每个线程都是独占SimpleDateFormat对象的,相当于把多线程程序变成了“单线程”程序,所以不会有线程不安全感。具体实现代码如下:importjava.text.SimpleDateFormat;importjava.util.Date;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;publicclassSimpleDateFormatExample{publicstaticvoidmain(String[]args){//创建一个线程池ExecutorServicethreadPool=Executors.newFixedThreadPool(10);//执行10次格式化for(inti=0;i<10;i++){intfinalI=i;//线程池执行任务threadPool.execute(newRunnable(){@Overridepublicvoidrun(){//创建一个SimpleDateFormat对象SimpleDateFormatsimpleDateFormat=newSimpleDateFormat("mm:ss");//创建一个时间对象Datedate=newDate(finalI*1000);//执行时间格式化并打印结果System.out.println(simpleDateFormat.format(date));}});}//任务执行完后关闭线程池threadPool.shutdown();}}上面程序的执行结果是:打印出来的结果都不同说明程序的执行是正确的,从上面的结果可以看出that将SimpleDateFormat定义为局部变量后,线程不安全的问题就可以顺利解决。②使用同步锁是解决线程不安全问题最常用的方法。接下来,我们首先使用synchronized来锁定时间格式化。实现代码如下:importjava.text.SimpleDateFormat;importjava.util.Date;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;publicclassSimpleDateFormatExample2{//创建SimpleDateFormat对象privatestaticSimpleDateFormatsimpleDateFormat=newSimpleDateFormat("mm:SS");publicstaticvoidmain(String[]args){//创建线程池ExecutorPoolServicethreadPool=newThedreads10);//执行10次formatfor(inti=0;i<10;i++){intfinalI=i;//线程池执行任务threadPool.execute(newRunnable(){@Overridepublicvoidrun(){//创建时间对象Datedate=newDate(finalI*1000);//定义格式化结果Stringresult=null;synchronized(simpleDateFormat){//时间格式化结果=simpleDateFormat.format(date);}//打印结果System.out.println(result);}});}//任务执行完后关闭线程池threadPool.shutdown();}}以上的执行结果程序是:③使用Lock进行加锁在Java语言中,锁的常见实现方式有:二、除了synchronized,还可以使用手动加锁Lock,那么我们使用Lock改造线程不安全的代码,实现代码如下:importjava.text.SimpleDateFormat;importjava.util.Date;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;importjava.util.concurrent.locks.Lock;importjava.util.concurrent.locks.ReentrantLock;/***锁解决线程不安全*/publicclassSimpleDateFormatExample3{//创建一个SimpleDateFormat对象privatestaticSimpleDateFormatsimpleDateFormat=newSimpleDateFormat("mm:ss");publicstaticvoidmain(String[]args){//创建线程池ExecutorServicethreadPool=Executors.newFixedThreadPool(10);//创建LocklockLocklock=newReentrantLock();//执行10次formatfor(inti=0;i<10;i++){intfinalI=i;//线程池执行任务threadPool.execute(newRunnable(){@Overridepublicvoidrun(){//创建时间对象Datedate=newDate(finalI*1000);//定义格式化结果Stringresult=null;//加锁lock.lock();try{//时间格式result=simpleDateFormat.format(date);}finally{//释放锁lock.unlock();}//打印结果System.out.println(result);}});}//任务执行完后关闭线程池threadPool.shutdown();}}上面程序的执行结果为:从上面的代码可以看出,手动锁比同步更麻烦④虽然使用ThreadLocal锁方案可以正确解决线程不安全的问题,但同时也引入了新的问题。加锁会让程序进入排队执行的过程,从而在一定程度上降低程序的执行效率,如下图所示:有没有解决线程不安全问题的方案,避免排队执行在同一时间?答案是肯定的,可以考虑使用ThreadLocal。ThreadLocal翻译成中文是线程局部变量的意思。ThreadLocal一词用于创建线程的私有(局部)变量。每个线程都有自己的私有对象,这样就可以避免线程不安全的问题。实现如下:知道了实现方案后,下面用具体的代码来演示一下ThreadLocal的使用。实现代码如下:importjava.text.SimpleDateFormat;importjava.util.Date;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;/***ThreadLocal解决线程不安全*/publicclassSimpleDateFormatExample4{//Create一个ThreadLocal对象并设置默认值(newSimpleDateFormat)privatestaticThreadLocal
