多个线程使用相同的SimpleDateFormat而不使用ThreadLocal

时间:2016-09-07 07:12:25

标签: java multithreading thread-local

我想了解使用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

4 个答案:

答案 0 :(得分:3)

关键点是:SimpleDateFormat实现线程安全。

这并不意味着它会引发异常。 它是更糟:也许,偶尔共享格式化程序只会给你错误的输出!

你知道,如果&#34;多线程问题&#34;很好地向你抛出异常......人们不会那么害怕他们。因为我们会直接暗示出现问题。

相反,事情出错了 - 未被注意

建议:将测试增强到

  1. 始终格式化相同日期对象
  2. 检查格式 日期的结果是否符合预期(例如,将其与初始第一次格式化操作的结果进行比较)
  3. 当然:只打印不匹配,以便在发生时发出通知。或者更好:在不匹配时抛出自己的异常!

    编辑:事实证明&#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();
    }
  }
}

如评论所示,每个偶数编号的线程将格式化昨天的日期,奇数编号的线程将使用明天的日期。

如果你运行它,线程几乎会立即通过抛出异常开始自杀,直到你只剩下少数几个,可能所有格式化相同的日期。