我想了解使用ThreadLocal
的必要性。很多人提到ThreadLocal
应该用来提供每线程SimpleDateFormat
,但他们没有提到如果不使用SimpleDateFormat
将会看到错误的ThreadLocal
。我尝试下面的代码,看起来很好,我没有看到错误的SimpleDateFormat
。
import java.text.SimpleDateFormat;
import java.util.Date;
public class ThreadLocalTest {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
private static final Date TODAY = new Date();
private static final String expected = "07/09/2016";
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
String real = dateFormat.format(TODAY);
if (!real.equals(expected)) {
throw new RuntimeException("Mangled SimpleDateFormat");
}
}
}
}).start();
}
}
}
如何生成NumberFormatException
之类的例外,因为我不使用ThreadLocal
?
答案 0 :(得分:3)
关键点是:SimpleDateFormat实现不线程安全。
这并不意味着它会引发异常。 它是更糟:也许,偶尔共享格式化程序只会给你错误的输出!
你知道,如果&#34;多线程问题&#34;很好地向你抛出异常......人们不会那么害怕他们。因为我们会直接暗示出现问题。
相反,事情出错了 - 未被注意。
建议:将测试增强到
当然:只打印不匹配,以便在发生时发出通知。或者更好:在不匹配时抛出自己的异常!
编辑:事实证明&#34;更好&#34;强制执行不一致的方法是不使用格式化但解析!
最后,要解决另一个评论:当然,只有在多个线程之间共享的对象才会出现不一致。当每个线程都有自己的格式对象时,没有共享;因此没问题。
答案 1 :(得分:0)
只需运行这些代码,您将获得“java.lang.NumberFormatException”。如果没有发生,再多跑几次
import java.text.ParseException;
import java.text.SimpleDateFormat;
public class ThreadLocalDemo1 implements Runnable {
private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
ThreadLocalDemo1 td = new ThreadLocalDemo1();
Thread t1 = new Thread(td, "Thread-1");
Thread t2 = new Thread(td, "Thread-2");
t1.start();
t2.start();
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("Thread run execution started for " + Thread.currentThread().getName());
System.out.println("Date formatter pattern is " + simpleDateFormat.toPattern());
try {
System.out.println("Formatted date is " + simpleDateFormat.parse("2013-05-24 06:02:20"));
} catch (ParseException pe) {
pe.printStackTrace();
}
System.out.println("=========================================================");
}
}
}
答案 2 :(得分:0)
日期格式不是线程安全的。
我认为如果你在同一天格式化你无法复制,你应该使用2个不同的日期或格式也是第二个和日期有不同的秒等。日期格式使用引擎盖下的日历设置日期。如果第一个线程设置了一个日期并开始格式化该字符串,而另一个具有不同日期的线程出现并将其设置在同一个日历上,则输出错误。
以下代码产生异常/错误:
final Date today = new Date();
String expectedToday = dateFormat.format(today);
Date yesterday = new Date(today.getTime() - TimeUnit.DAYS.toMillis(1));
String expectedYesterday = dateFormat.format(yesterday);
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
String format = dateFormat.format(today);
if (!expectedToday.equals(format)) {
System.out.println("error: " + format + " " + expectedToday);//Throw exception if you want
}
format = dateFormat.format(yesterday);
if (!expectedYesterday.equals(format)) {
System.out.println("error: " + format + " " + expectedYesterday);//Throw exception if you want
}
}
}
}).start();
}
答案 3 :(得分:0)
SimpleDateFormat
不是线程安全的方法之一是它有一个内部calendar
字段,它包含一个Calendar
对象。实际格式化日期之前SimpleDateFormat
所做的第一件事就是调用this.calendar.setTime(theDateYouPassedIn)
,没有同步或锁定。我不确定这是否是唯一的方法,但检查代码应该相当简单。
因此,让SimpleDateFormat
失败的一种方法是使用会在不同线程中产生不同输出的日期。这是一个例子:
public class NotThreadSafe
{
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
public static void main(String[] args) {
Date dref = new Date();
// Dates for yesterday and tomorrow
Date[] ds = new Date[] {
new Date(dref.getTime() - (24L * 60L * 60L * 1000L)),
new Date(dref.getTime() + (24L * 60L * 60L * 1000L))
};
String[] refs = new String[ds.length];
for (int i = 0; i < ds.length; ++i) {
// How the corresponding ds[i] should be formatted
refs[i] = dateFormat.format(ds[i]);
}
for (int i = 0; i < 100; i++) {
// Every even numbered thread uses ds[0] and refs[0],
// odd numbered threads use ds[1] and refs[1].
int index = (i % 2);
final Date d = ds[index];
final String ref = refs[index];
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
String s = dateFormat.format(d);
if (!ref.equals(s)) {
throw new IllegalStateException("Expected: " + ref + ", got: " + s);
}
}
}
}).start();
}
}
}
如评论所示,每个偶数编号的线程将格式化昨天的日期,奇数编号的线程将使用明天的日期。
如果你运行它,线程几乎会立即通过抛出异常开始自杀,直到你只剩下少数几个,可能所有格式化相同的日期。