【问题标题】:How to know if a Java SE class or method is thread safe?如何知道 Java SE 类或方法是否是线程安全的?
【发布时间】:2015-08-15 09:23:35
【问题描述】:

例如:

static private DateFormat df = new SimpleDateFormat();
public static void format(final Date date) { 
   for (int i = 0; i < 10; i++) 
     new Thread(new Runnable() {
         public void run() {
             System.out.println(df.format(date));
         } 
     });
}

DateFormat 类被记录为不是同步类,但是如果我们只使用格式方法,它不能改变整个类的状态吗?

假设它被声明为私有,如何确定代码是线程安全的?

修复此代码的最佳方法是什么?:

  1. 为每个线程使用不同的实例。

  2. 使用同步块。

【问题讨论】:

  • 通过仔细阅读文档,无论是类文档还是具体方法文档。
  • DateFormat 特别提到它不是线程安全的,如果我没记错的话。
  • 好吧,记录为非同步类,如果我们只使用格式方法,它不能改变洞类的状态吗?
  • 如果一个类被记录为非线程安全的,就像 DateFormat 一样,你就不要再猜测哪种方法实际上可能是线程安全的,也可能不是。您只是不在多个线程之间共享实例。顺便说一句,format() 改变或不改变状态的事实是无关紧要的:如果同时调用另一个方法,并且该方法改变了状态,那么 format() 将破坏其不变量,并可能返回错误的结果,或抛出异常,或其他任何东西。
  • 阅读方法的源代码并了解它是如何工作的会很有用,而且源代码可以在 Internet 上免费获得。但是,您不能依赖实现在版本中保持不变。如果你的程序不允许任何其他线程接触一个对象,你为什么要关心线程安全?

标签: java multithreading thread-safety


【解决方案1】:
  • 对于标准的 Java SE 类,了解该类是否线程安全的最佳方法是仔细阅读其文档。始终阅读类文档和方法文档。如果说它不是同步的或不是线程安全的,你就知道它不是线程安全的。
  • 因此,DateFormat 类不是线程安全的。文档特别说:

    日期格式不同步。建议为每个线程创建单独的格式实例。如果多个线程同时访问一个格式,必须在外部同步。

  • 声明一个字段private 确实不会使您的实现线程安全。 private 只是说外部班级看不到该字段。让我们看看你的方法:

     for (int i=0;i<10;i++) 
         new Thread(new Runnable(){
             public void run(){
                 System.out.println(df.format(date));
             } 
         });
    

    您创建的Runnable 对象是匿名类。匿名类是 inner 类,它们可以访问其周围类的私有字段。如果不是这样,您的程序将无法编译 - 他们无法访问 df 字段。

    但他们可以。所以实际上你有 10 个线程都在访问你的一个 DateFormat 对象,由 df 引用。由于我们已经知道DateFormat 是非线程安全的,所以你的程序不是线程安全的。

  • 此外,如果两个外部线程引用了您的对象(我的意思是其中包含df 的对象。您没有给出类声明,所以我不知道它的名称是什么)。它们引用了你的类的同一个实例。如果两者同时调用format,则两者都将使用相同的私有df 运行DateFormat.format。因此,这不会是线程安全的。
  • 为了线程安全,您需要在对象上进行同步或使用其他类型的锁(一个锁用于所有可能访问它的线程),这正是文档中所说的。
  • 另一种方法是拥有一个完全本地的对象,它只对一个线程可见。不是一个字段 - 一个局部变量,它可以访问唯一创建的 DateFormat 实例(因此每次调用该方法时都有一个新副本)。不过要小心匿名类!在您的示例中,即使 df 是 format 方法的本地字段,它仍然不是线程安全的,因为您的所有线程都将访问同一个副本。

【讨论】:

  • 所以即使实例是我的方法 Format 的本地实例,并且我确信所有线程都只使用 Format 方法,我认为它是无状态的,它仍然不是线程安全的吗?
  • @NassimMOUALEK 你不能假设。这仍然不是线程安全的。如果文档说它不是,那么它不是。多个线程访问实例 = 不是线程安全的。
  • 是的,但他们没有指定每种方法,他们将孔类记录为未同步,但我很确定格式日期应该是无状态的
  • 在这种特殊情况下,我个人会使用同步块,因为System.out.println 无论如何都会变慢,并且还涉及同步,因此同步延迟可以忽略不计。此外,复制也是有代价的,这通常只有在线程多次使用实例的情况下才合理。
  • 我的意思是它有自己的同步块。
【解决方案2】:

根据文档,该格式不是线程安全的。

同步

日期格式不同步。建议为每个线程创建单独的格式实例。如果多个线程同时访问一个格式,必须在外部同步。

Date format

如何阅读?如果您没有明确保证某些方法是线程安全的(或记录为不安全的),那么您不能对它是安全的做出任何假设。

但是,如果您真的希望只使用可能没有状态的单一方法,您始终可以创建高并发环境并在同步和不同步的情况下测试数据完整性。

我对密码和 RSA 有过类似的问题。 那里的答案显示了一种如何为此测试java SE类的特定方法的方法。但是请注意,实现可能随时发生变化,并且根据实现细节而不是接口进行自己的实现可能会在未来导致一些不可预知的问题。

testing for data integrity

【讨论】:

    【解决方案3】:

    我知道这很难相信,但 DateFormat.format() 实际上修改了 DateFormat 的状态。例如,对于 SimpleDateFormat:

    // 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);
    

    calendar 是 DateFormat 的一个字段。

    因此,我只能建议您相信文档。它可能知道你不知道的事情。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-22
      相关资源
      最近更新 更多