本文首发于 http://www.YoungZY.com/
就是多个线程会对某个变量同时执行读/写操作的时候。
问题
举个常用但没太注意过的例子 —— SimpleDateFormat 类。
这个类是JDK里面封装的,用来对日期进行格式化。提供 parse(String dateStr)
, format(Date date)
等方法。
注:上面提到的方法都在其父类 DateFormat 中。
翻开源代码就会发现,它有一个实体变量 calendar
(也是在父类 DateFormat 中)。上面提到的 parse 、format 等方法会对这个变量执行 clear() 、set…(…) 等操作(具体操作请查看源代码)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public abstract class DateFormat extends Format { /** * 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; ... } |
这样,如果有一个类型为 SimpleDateFormat 的公共变量,就要小心了,这个变量不是线程安全的,多个线程间的数据可能会串了…几乎是一定会串。
例如,有这样一个日期的工具类,就是将日期转换成特定的字符串格式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/** * * * @author by youngz * on 2016年11月27日 * * Package&FileName: org.young.thread.DateUtil */ public class DateUtil { public final static String PATTERN_YMD = "yyyy-MM-dd"; private static SimpleDateFormat formatter = new SimpleDateFormat(); public static String getFormattedDate(Date date) { formatter.applyPattern(PATTERN_YMD); return formatter.format(date); } } |
这是测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
/** * 测试类 * * @author by youngz * on 2016年11月27日 * * Package&FileName: org.young.thread.Main */ public class Main { public static void main(String[] args) { // 昨天的日期 Calendar calYesterday = Calendar.getInstance(); calYesterday.setTime(new Date()); calYesterday.add(Calendar.DAY_OF_MONTH, -1); ShowDate yesterday = new ShowDate("YESTERDAY", calYesterday.getTime()); // 今天的日期 ShowDate today = new ShowDate("TODAY", new Date()); Thread t1 = new Thread(yesterday); Thread t2 = new Thread(today); /* * start() * 它的作用是启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用。 * run() * 就和普通的成员方法一样,可以被重复调用。单独调用s的话,会在当前线程中执行run(),而并不会启动新线程! */ t1.start(); t2.start(); } } |
还有一个线程类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
/** * 一个简单的线程的实现类 * * @author by youngz * on 2016年11月27日 * * Package&FileName: org.young.thread.ShowDate */ class ShowDate implements Runnable { private String desc; private Date date; public ShowDate(String desc, Date date) { this.desc = desc; this.date = date; } @Override public void run() { for (int i = 0; i < 1000; i++) { if (i % 30 == 0) { try { Thread.sleep(500L); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(desc + ": " + DateUtil.getFormattedDate(date)); } } } |
按照设想,以今天(2016-11-27)为例,应该是 YESTERDAY: 2016-11-26 和 TODAY: 2016-11-27 这两个字符串交叉着,各自打印 1000 遍。
可事实是这样吗?
运行一下看看。
结果简直惨不忍睹,第一行就出错了(当然,这个可能需要一定的“运气”)。“今天”一会儿 26,一会儿 27。“昨天”也一样,一会儿 26,一会儿 27。
这就是线程不安全导致的问题了。
该怎么解决呢??
解决办法(一) —— synchronized
比较容易想到的就是这个关键字了:synchronized 。在学校就是这么学的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/** * 进化的 DateUtil * 使用同步块,synchronized * * @author by youngz * on 2016年11月27日 * * Package&FileName: org.young.thread.SyncDateUtil */ public class SyncDateUtil { public final static String PATTERN_YMD = "yyyy-MM-dd"; private static SimpleDateFormat formatter = new SimpleDateFormat(); public static String getFormattedDate(Date date) { synchronized(formatter) { formatter.applyPattern(PATTERN_YMD); return formatter.format(date); } } } |
只要把线程类 —— ShowDate 的第 30 行的工具类替换一下就行,DateUtil 换成 SyncDateUtil ,即:
1 2 |
// System.out.println(desc + ": " + DateUtil.getFormattedDate(date)); System.out.println(desc + ": " + SyncDateUtil.getFormattedDate(date)); |
在运行试试!
不管执行多少遍,都不会出现前面说的串数据的问题了。
解决办法(二) —— ThreadLocal
除了 synchronized 还有 ThreadLocal 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/** * 进化的 DateUtil —— ThreadLocal * * @author by youngz * on 2016年11月27日 * * Package&FileName: org.young.thread.ThreadLocalDateUtil */ public class ThreadLocalDateUtil { public final static String PATTERN_YMD = "yyyy-MM-dd"; private static ThreadLocal<DateFormat> formatter = new ThreadLocal<DateFormat>() { protected DateFormat initialValue() { return new SimpleDateFormat(PATTERN_YMD); } }; public static String getFormattedDate(Date date) { return formatter.get().format(date); } } |
ThreadLocal 还有另一种实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
/** * 进化的 DateUtil —— ThreadLocal 的另一种实现 * * @author by youngz * on 2016年11月27日 * * Package&FileName: org.young.thread.ThreadLocalDateUtil2 */ public class ThreadLocalDateUtil2 { public final static String PATTERN_YMD = "yyyy-MM-dd"; private static ThreadLocal<DateFormat> formatter = new ThreadLocal<DateFormat>(); public static DateFormat getDateFormat() { DateFormat df = formatter.get(); if (null == df) { df = new SimpleDateFormat(PATTERN_YMD); formatter.set(df); } return df; } public static String getFormattedDate(Date date) { return getDateFormat().format(date); } } |
详细代码可参照 https://github.com/youngzhu/CollectionCode4Java/tree/master/src/org/young/thread
参考:
http://www.cnblogs.com/doit8791/p/4093808.html
加入讨论