【多線程補充】SimpleDateFormat非線程安全與線程中、線程組中異常的處理

1.SimpleDateFormat非線程安全的問題

  類SimpleDateFormat主要負責日期的轉換與格式化,但在多線程環境中,使用此類容易形成數據轉換及處理的不正確,由於SimpleDateFormat類並非線程安全的。java

1.多線程中存在的問題:

package cn.qlq.thread.seventeen; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo1 extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(Demo1.class); private SimpleDateFormat simpleDateFormat; private String dateStr; public Demo1(SimpleDateFormat simpleDateFormat, String dateStr) { super(); this.simpleDateFormat = simpleDateFormat; this.dateStr = dateStr; } @Override public void run() { try { Date parse = simpleDateFormat.parse(dateStr); String format = simpleDateFormat.format(parse).toString(); LOGGER.info("threadName ->{} ,dateStr ->{},格式化後的->{}", Thread.currentThread().getName(), dateStr, format); } catch (ParseException e) { LOGGER.error("parseException", e); } } public static void main(String[] args) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); String[] dateStrs = new String[] { "2018-01-01", "2018-01-03", "2018-01-03" }; Thread[] threads = new Thread[3]; for (int i = 0; i < 3; i++) { threads[i] = new Demo1(simpleDateFormat, dateStrs[i]); } for (int i = 0; i < 3; i++) { threads[i].start(); } } }

結果:git

10:07:27 [cn.qlq.thread.seventeen.Demo1]-[INFO] threadName ->Thread-0 ,dateStr ->2018-01-01,格式化後的->2001-01-01
10:07:27 [cn.qlq.thread.seventeen.Demo1]-[INFO] threadName ->Thread-1 ,dateStr ->2018-01-03,格式化後的->2001-01-03
10:07:27 [cn.qlq.thread.seventeen.Demo1]-[INFO] threadName ->Thread-2 ,dateStr ->2018-01-03,格式化後的->2001-01-01apache

 

屢次執行發現結果不固定,有時候報錯以下:安全

Exception in thread "Thread-1" Exception in thread "Thread-0" java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1110) at java.lang.Double.parseDouble(Double.java:540) at java.text.DigitList.getDouble(DigitList.java:168) at java.text.DecimalFormat.parse(DecimalFormat.java:1321) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1793) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1455) at java.text.DateFormat.parse(DateFormat.java:355) at cn.qlq.thread.seventeen.Demo1.run(Demo1.java:24) java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1110) at java.lang.Double.parseDouble(Double.java:540) at java.text.DigitList.getDouble(DigitList.java:168) at java.text.DecimalFormat.parse(DecimalFormat.java:1321) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1793) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1455) at java.text.DateFormat.parse(DateFormat.java:355) at cn.qlq.thread.seventeen.Demo1.run(Demo1.java:24) 10:08:20 [cn.qlq.thread.seventeen.Demo1]-[INFO] threadName ->Thread-2 ,dateStr ->2018-01-03,格式化後的->2200-01-03

  由於上面多個線程使用了同一個SimpleDateFormat實例,因此在多線程環境下使用此類是不安全的。多線程

 

補充:SimpleDateFormat線程非安全的緣由:app

  SimpleDateFormat內部繼承了一個DateFormat的calendar成員變量,因此多個線程共用同一個calendar,因此容易形成線程非安全。less

public class SimpleDateFormat extends DateFormat { // 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); ... } } public abstract class DateFormat extends Format { protected Calendar calendar; }

 

補充:再次查看apache的commons-lang包的工具類:DateFormatUtilsdom

   其方法內部新建了一個局部變量,並進行格式化,所以不存在線程非安全的問題ide

public static String format(final Date date, final String pattern, final TimeZone timeZone, final Locale locale) { final FastDateFormat df = FastDateFormat.getInstance(pattern, timeZone, locale); return df.format(date); }
public String format(final Date date) { final Calendar c = newCalendar();  // hard code GregorianCalendar
 c.setTime(date); return applyRulesToString(c); }

 

2.解決辦法

1.每一個線程使用一個SimpleDateFormat

修改main方法中的代碼以下:(每一個線程使用一個SimpleDateFormat)工具

String[] dateStrs = new String[] { "2018-01-01", "2018-01-03", "2018-01-03" }; Thread[] threads = new Thread[3]; for (int i = 0; i < 3; i++) { threads[i] = new Demo2(new SimpleDateFormat("yyyy-MM-dd"), dateStrs[i]); } for (int i = 0; i < 3; i++) { threads[i].start(); }

 

2.使用ThreadLocal解決上面問題

  在前面學習過ThreadLocal的做用,能夠實現多線程之間的隔離。

package cn.qlq.thread.seventeen; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo2 extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(Demo2.class); private static final ThreadLocal<SimpleDateFormat> THREAD_LOCAL = new ThreadLocal<SimpleDateFormat>(); public static SimpleDateFormat getSimpleDateFormat() { SimpleDateFormat simpleDateFormat2 = THREAD_LOCAL.get(); if (simpleDateFormat2 == null) { simpleDateFormat2 = new SimpleDateFormat("yyyy-MM-dd"); THREAD_LOCAL.set(simpleDateFormat2); } return simpleDateFormat2; } private SimpleDateFormat simpleDateFormat; private String dateStr; public Demo2(String dateStr) { super(); this.dateStr = dateStr; } @Override public void run() { try { // 從ThreadLocal中獲取simpleDateFormat
            this.simpleDateFormat = Demo2.getSimpleDateFormat(); Date parse = simpleDateFormat.parse(dateStr); String format = simpleDateFormat.format(parse).toString(); LOGGER.info("threadName ->{} ,dateStr ->{},格式化後的->{}", Thread.currentThread().getName(), dateStr, format); } catch (ParseException e) { LOGGER.error("parseException", e); } } public static void main(String[] args) { String[] dateStrs = new String[] { "2018-01-01", "2018-01-03", "2018-01-03" }; Thread[] threads = new Thread[3]; for (int i = 0; i < 3; i++) { threads[i] = new Demo2(dateStrs[i]); } for (int i = 0; i < 3; i++) { threads[i].start(); } } }

結果:

10:37:53 [cn.qlq.thread.seventeen.Demo2]-[INFO] threadName ->Thread-1 ,dateStr ->2018-01-03,格式化後的->2018-01-03
10:37:53 [cn.qlq.thread.seventeen.Demo2]-[INFO] threadName ->Thread-0 ,dateStr ->2018-01-01,格式化後的->2018-01-01
10:37:53 [cn.qlq.thread.seventeen.Demo2]-[INFO] threadName ->Thread-2 ,dateStr ->2018-01-03,格式化後的->2018-01-03

 

2.線程中出現異常的處理

  線程中也容易出現異常。在多線程中也能夠對多線程中的異常進行捕捉,使用的是UncaughtExceptionHandler類,從而能夠對發生的異常進行有效的處理。

package cn.qlq.thread.seventeen; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo3 extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(Demo3.class); @Override public void run() { int i = 1 / 0; } public static void main(String[] args) { new Demo3().start(); } }

結果:

Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
at cn.qlq.thread.seventeen.Demo3.run(Demo3.java:12)

 

  使用UncaughtExceptionHandler捕捉多線程中的異常。查看線程類Thread的源碼,發現其有兩個異常處理器,一個是全部線程共享的默認靜態異常處理器、另外一個是線程獨有的成員屬性。

// null unless explicitly set
    private volatile UncaughtExceptionHandler uncaughtExceptionHandler; // null unless explicitly set
    private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler; 
  
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission( new RuntimePermission("setDefaultUncaughtExceptionHandler") ); } defaultUncaughtExceptionHandler = eh; } public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) { checkAccess(); uncaughtExceptionHandler = eh; }

 

(1)多全部線程設置默認的默認異常處理器

package cn.qlq.thread.seventeen; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo5 extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(Demo5.class); @Override public void run() { int i = 1 / 0; } public static void main(String[] args) { Demo5.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { LOGGER.error("全部Demo5線程默認的異常處理器, threadName -> {}", t.getName(), e); } }); Demo5 demo5 = new Demo5(); demo5.start(); } }

結果:

14:12:54 [cn.qlq.thread.seventeen.Demo5]-[ERROR] 全部Demo5線程默認的異常處理器, threadName -> Thread-0
java.lang.ArithmeticException: / by zero
at cn.qlq.thread.seventeen.Demo5.run(Demo5.java:12)

 

(2)對單個線程設置異常處理器

package cn.qlq.thread.seventeen; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo5 extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(Demo5.class); @Override public void run() { int i = 1 / 0; } public static void main(String[] args) { Demo5 demo5 = new Demo5(); // 設置異常捕捉器
        demo5.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { LOGGER.error("單獨給線程設置的, threadName -> {}", t.getName(), e); } }); demo5.start(); } }

結果:

14:15:00 [cn.qlq.thread.seventeen.Demo5]-[ERROR] 單獨給線程設置的, threadName -> Thread-0
java.lang.ArithmeticException: / by zero
at cn.qlq.thread.seventeen.Demo5.run(Demo5.java:12)

 

(3)若是都設置查看默認的生效的異常處理器---是單獨設置的異常處理器uncaughtExceptionHandler發揮做用

package cn.qlq.thread.seventeen; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo5 extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(Demo5.class); @Override public void run() { int i = 1 / 0; } public static void main(String[] args) { // 設置全局的
        Demo5.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { LOGGER.error("全部Demo5線程默認的異常處理器, threadName -> {}", t.getName(), e); } }); Demo5 demo5 = new Demo5(); // 設置單獨的
        demo5.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { LOGGER.error("單獨給線程設置的, threadName -> {}", t.getName(), e); } }); demo5.start(); } }

結果:(生效的是線程單獨設置的異常處理器)

14:19:25 [cn.qlq.thread.seventeen.Demo5]-[ERROR] 單獨給線程設置的, threadName -> Thread-0
  java.lang.ArithmeticException: / by zero
  at cn.qlq.thread.seventeen.Demo5.run(Demo5.java:12)

 

 

3.線程組出現異常的處理

3.1線程組內出現異常

package cn.qlq.thread.seventeen; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo3 extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(Demo3.class); private Integer num; public Demo3(Integer num, ThreadGroup threadGroup) { super(threadGroup, num + "" + Math.random()); this.num = num; } @Override public void run() { int i = 1 / num; while (true) { try { Thread.sleep(1 * 1000); LOGGER.info("num = {}", num++); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { ThreadGroup threadGroup = new ThreadGroup("myGroup"); Thread t1 = new Demo3(0, threadGroup); Thread t2 = new Demo3(0, threadGroup); Thread t3 = new Demo3(1, threadGroup); t1.start(); t2.start(); t3.start(); } }

結果:(t1,t2因爲異常中止,t3仍然在執行while循環)。

查看線程信息

Administrator@MicroWin10-1535 MINGW64 /e/xiangmu/ThreadStudy (master) $ jstack 2712
2019-01-03 12:00:26 Full thread dump Java HotSpot(TM) 64-Bit Server VM (24.80-b11 mixed mode): "DestroyJavaVM" prio=6 tid=0x000000000c8f5000 nid=0xbc8 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "10.0050164194939836815" prio=6 tid=0x000000000c8f6800 nid=0x54d4 waiting on condition [0x000000000ce4f000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at cn.qlq.thread.seventeen.Demo3.run(Demo3.java:21) "Service Thread" daemon prio=6 tid=0x000000000ae14000 nid=0x31d0 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread1" daemon prio=10 tid=0x000000000adeb800 nid=0x754 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread0" daemon prio=10 tid=0x000000000adea000 nid=0x22e4 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Attach Listener" daemon prio=10 tid=0x000000000ade8800 nid=0x51c waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Signal Dispatcher" daemon prio=10 tid=0x000000000ae01000 nid=0x2914 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Finalizer" daemon prio=8 tid=0x000000000adae800 nid=0x1ef8 in Object.wait() [0x000000000c14f000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000007d6204858> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135) - locked <0x00000007d6204858> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209) "Reference Handler" daemon prio=10 tid=0x000000000ada5800 nid=0x3820 in Object.wait() [0x000000000c04f000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000007d6204470> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:503) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133) - locked <0x00000007d6204470> (a java.lang.ref.Reference$Lock) "VM Thread" prio=10 tid=0x000000000ada1800 nid=0x2f54 runnable "GC task thread#0 (ParallelGC)" prio=6 tid=0x0000000002a76800 nid=0x4d04 runnable "GC task thread#1 (ParallelGC)" prio=6 tid=0x0000000002a78000 nid=0x1bbc runnable "GC task thread#2 (ParallelGC)" prio=6 tid=0x0000000002a7b000 nid=0x11a0 runnable "GC task thread#3 (ParallelGC)" prio=6 tid=0x0000000002a7c800 nid=0x4b80 runnable "VM Periodic Task Thread" prio=10 tid=0x000000000ae1e800 nid=0x5a4 waiting on condition JNI global references: 184

 

 

3.2 線程組內處理異常

   從上面的運行結果也能夠看出線程組中的一個線程出現異常不會影響其它線程的運行。

 1.線程組統一對異常進行處理,記錄異常

  繼承ThreadGroup而且重寫uncaughtException方法。

package cn.qlq.thread.seventeen; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo3 extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(Demo3.class); private Integer num; public Demo3(Integer num, ThreadGroup threadGroup) { super(threadGroup, num + "" + Math.random()); this.num = num; } @Override public void run() { int i = 1 / num; while (true) { try { Thread.sleep(1 * 1000); LOGGER.info("num = {}", num++); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { MyThreadGroup threadGroup = new MyThreadGroup(new ThreadGroup("myGroup")); Thread t1 = new Demo3(0, threadGroup); Thread t2 = new Demo3(0, threadGroup); Thread t3 = new Demo3(1, threadGroup); t1.start(); t2.start(); t3.start(); } } class MyThreadGroup extends ThreadGroup { private static final Logger LOGGER = LoggerFactory.getLogger(MyThreadGroup.class); public MyThreadGroup(ThreadGroup threadGroup) { super(threadGroup, threadGroup.getName()); } @Override public void uncaughtException(Thread t, Throwable e) { LOGGER.error("UncaughtException , threadName -> {},ThreadGroupName -> {}", t.getName(), t.getThreadGroup().getName(), e); } }

結果:

 

 2.線程組處理異常,一個線程異常中止組內全部異常

package cn.qlq.thread.seventeen; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo3 extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(Demo3.class); private Integer num; public Demo3(Integer num, ThreadGroup threadGroup) { super(threadGroup, num + "" + Math.random()); this.num = num; } @Override public void run() { int i = 1 / num; while (!this.isInterrupted()) { LOGGER.info("num = {}", num++); } } public static void main(String[] args) { MyThreadGroup threadGroup = new MyThreadGroup(new ThreadGroup("myGroup")); Thread t1 = new Demo3(0, threadGroup); Thread t2 = new Demo3(0, threadGroup); Thread t3 = new Demo3(1, threadGroup); t1.start(); t2.start(); t3.start(); } } class MyThreadGroup extends ThreadGroup { private static final Logger LOGGER = LoggerFactory.getLogger(MyThreadGroup.class); public MyThreadGroup(ThreadGroup threadGroup) { super(threadGroup, threadGroup.getName()); } @Override public void uncaughtException(Thread t, Throwable e) { LOGGER.error("UncaughtException , threadName -> {},ThreadGroupName -> {},向組發出中斷信號", t.getName(), t.getThreadGroup().getName(), e); this.interrupt(); } }

結果:

12:14:56 [cn.qlq.thread.seventeen.Demo3]-[INFO] num = 1
12:14:56 [cn.qlq.thread.seventeen.MyThreadGroup]-[ERROR] UncaughtException , threadName -> 00.03911857279295183,ThreadGroupName -> myGroup,向組發出中斷信號
java.lang.ArithmeticException: / by zero
at cn.qlq.thread.seventeen.Demo3.run(Demo3.java:18)
12:14:56 [cn.qlq.thread.seventeen.MyThreadGroup]-[ERROR] UncaughtException , threadName -> 00.9116153677767221,ThreadGroupName -> myGroup,向組發出中斷信號
java.lang.ArithmeticException: / by zero
at cn.qlq.thread.seventeen.Demo3.run(Demo3.java:18)
12:14:56 [cn.qlq.thread.seventeen.Demo3]-[INFO] num = 2

 

4.線程異常處理的傳遞

  前面介紹了多種線程的處理方式,若是將多個異常處理方式放在一塊兒運行,若是線程設置了異常處理首先生效的是線程的異常處理器;若是線程沒有設置,生效的是線程組的異常處理器。

package cn.qlq.thread.seventeen; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo4 extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(Demo4.class); private Integer num; public Demo4(Integer num, ThreadGroup threadGroup) { super(threadGroup, num + "" + Math.random()); this.num = num; } @Override public void run() { int i = 1 / num; while (!this.isInterrupted()) { LOGGER.info("num = {}", num++); } } public static void main(String[] args) { MyThreadGroup2 threadGroup = new MyThreadGroup2(new ThreadGroup("myGroup")); Thread t1 = new Demo4(0, threadGroup); t1.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { LOGGER.error("線程中報錯,threadName->{}", t.getName(), e); } }); t1.start(); Thread t2 = new Demo4(0, threadGroup); t2.start(); } } class MyThreadGroup2 extends ThreadGroup { private static final Logger LOGGER = LoggerFactory.getLogger(MyThreadGroup2.class); public MyThreadGroup2(ThreadGroup threadGroup) { super(threadGroup, threadGroup.getName()); } @Override public void uncaughtException(Thread t, Throwable e) { LOGGER.error("UncaughtException , threadName -> {},ThreadGroupName -> {}", t.getName(), t.getThreadGroup().getName(), e); } }

結果:(t1被線程處理器捕捉,t2被線程組處理器捕捉)

13:51:07 [cn.qlq.thread.seventeen.Demo4]-[ERROR] 線程中報錯,threadName->00.8052783699913576
java.lang.ArithmeticException: / by zero
at cn.qlq.thread.seventeen.Demo4.run(Demo4.java:18)
13:51:07 [cn.qlq.thread.seventeen.MyThreadGroup2]-[ERROR] UncaughtException , threadName -> 00.6447075404863479,ThreadGroupName -> myGroup
java.lang.ArithmeticException: / by zero
at cn.qlq.thread.seventeen.Demo4.run(Demo4.java:18)

 

若是在組中調用原來父類的方法會繼續在console中打印錯誤日誌:

class MyThreadGroup2 extends ThreadGroup { private static final Logger LOGGER = LoggerFactory.getLogger(MyThreadGroup2.class); public MyThreadGroup2(ThreadGroup threadGroup) { super(threadGroup, threadGroup.getName()); } @Override public void uncaughtException(Thread t, Throwable e) { LOGGER.error("UncaughtException , threadName -> {},ThreadGroupName -> {}", t.getName(), t.getThreadGroup().getName(), e); super.uncaughtException(t, e); } }

 結果:

相關文章
相關標籤/搜索