【问题标题】:On the thread safety of instance variable initialization关于实例变量初始化的线程安全
【发布时间】:2009-10-14 20:17:48
【问题描述】:

我经常看到这种初始化实例变量的习语

public class Test{
    private Long l = 1l;
    private MyClass mc = new MyClass();

    public Test(){}
    ...
 }

但我更喜欢

public class Test{
    private Long l;
    private MyClass mc;

    public Test(){
        l = 1l;
        mc = new MyClass();
    }
    ...
}

考虑到这些是非最终变量,这两种方法在线程安全方面是等效的还是比另一种“更”正确?

【问题讨论】:

  • 关于可读性;我希望您对 long 常量使用大写 L 或将其省略,因为编译器将值 1 提升为 long 没有问题。 (小写的 l 很容易与 1 混淆。)还要注意变体 1 中的语法错误:缺少标识符 mc :-)
  • 是的,是的,确实缺少 mc,但幸运的是你明白了......但是我的编译器从不允许我做 Long l = 1;......如果你的确实让我知道你正在使用哪个 javac。

标签: java thread-safety


【解决方案1】:

线程安全不是问题,因为这发生在构造阶段,两个线程不能构造同一个对象。好吧,如果你让this 从构造函数中逃逸,那么另一个线程可能会在构造过程中访问该对象,但你真的不应该那样做。在功能方面,这两个选项是相同的,因此即使存在线程安全问题,它们也会以相同的方式影响两者。

如果您需要执行一些无法在初始化程序中完成的计算,第一个选项,即在声明时初始化字段,并不总是可行(即使那样,如果您这样做,您也可以将初始化排除在构造函数之外)不过,在初始化程序块中)。但如果任何一种方式都可行,那么这纯粹是一个风格问题,我认为 Java 程序员之间没有明确的偏好,所以选择你认为更好的方式。

【讨论】:

  • +1 提到了在构造函数执行时实例变量可以被另一个线程操作的一种方式。但是,即使使这成为可能,也似乎是对构造函数的滥用。如果你必须做这么多的初始化,无论如何我都会把它放在一个单独的方法中。
  • 我只想提一下,您可以在 instance initializer 块(类主体内的 {} 块)中进行大量计算。但是,我不建议将它们用于问题中描述的一般情况(在执行 Java 版本的闭包时它们非常宝贵)。如果您需要计算,请在构造函数或静态工厂方法中进行。
  • @Daniel:不幸的是,人们在创建和启动线程时让“this”从构造函数中逃脱是很常见的(如果这实现了 Runnable),将自己添加为其他人的侦听器等。FindBugs 会捕获它但我经常看到人们无法理解为什么它很危险。
  • 不幸的是,这个答案是错误的。如果对象是不可变的,则将字段设为 final 将是最好的解决方案。 java.sun.com/docs/books/jls/third_edition/html/… 更多地说明了 final 字段在需要线程安全的不可变对象中的重要性。
【解决方案2】:

由于您的变量是实例变量,而不是类变量,因此在使用任何一种方法进行初始化期间都不会出现线程安全问题。如果有 Java 标准推荐的最佳实践,我相信其他人会加入。

【讨论】:

  • @atk:你是说如果那些是“静态”变量就会存在线程安全问题,据我所知,静态初始化根据 JMM 保证是线程安全的。
  • 当然,我们也不会在构造函数中初始化“静态”变量,所以它要么像上面那样初始化,要么在也是线程安全的静态块中初始化
  • @non sequitor:是的,静态变量会有线程安全风险。静态变量是类范围的,而不是特定于实例的。如果没有执行额外的锁定,在构造函数中初始化静态变量可能是非线程安全的。 “静态初始化”与“初始化静态变量”不同。 “静态初始化”是 OP 的第一个示例(如果它带有静态变量)或在“静态 {}”块中
  • @atk:我的第二条评论排除了在构造函数中初始化静态变量的可能性,我什至从未尝试过——在构造函数中初始化静态变量甚至不应该是合法的,违背概念的精神
  • @non sequitor:抱歉。我没有意识到您认为该问题已被排除 - 我认为您最初的反应仍然有效。但是,在某些情况下,在构造函数中使用或初始化静态变量可能是合适的。我意识到我们并没有谈论使用,但它可能值得一提。一个简单的使用示例是计算类的实例数。
【解决方案3】:

我认为这是个人喜好和项目编码标准的问题。

只要确保你只在一个地方初始化变量(无论是构造函数,还是内联)。

在构造函数中完成初始化工作可为您提供更好的异常处理位置。

【讨论】:

    【解决方案4】:

    它们都不是线程安全的。如果线程 A 构造了对象,那么线程 B 可能会也可能不会观察到未完全初始化的 Test 对象或 MyClass 对象。构造函数退出后的可见性保证仅适用于最终字段。

    见http://pveentjer.wordpress.com/2007/03/18/immutability-doesnt-guarantee-thread-safety/

    【讨论】:

      【解决方案5】:

      在线程安全方面,它们是等价的。两者都需要执行相同的指令,如果您更喜欢第二个(我同意您的偏好),那么我会使用它。如果您希望构造函数周围的线程安全,则需要围绕构造函数调用进行同步调用。

      【讨论】:

      • 正如 atk 所指出的,我不确定这里是否存在线程安全问题,除非对象存储在全局可访问的位置。
      • 如果你创建字段final(你使用的任何等效代码段)并且不要让this在构造函数结束时逃逸,那么实例将免受不安全出版物。实际上,不安全的发布相对较少(或者至少很少故意这样做)。
      • 我知道不安全的发布很少是故意的,我只是指出以防他们犯了这个错误——有时很难追查到 :)
      【解决方案6】:

      我不确定这是否早先得到了回答。但是,我对以下情况有疑问:

      我正在尝试创建一个 @Component 类,其中我有一个实例变量。现在,我想为每个请求创建一个实例变量的新对象。我不确定哪一种方法是正确的?

      **Option 1:**
      @Component
      public class ClassA {
      
      private ClassB classB = new ClassB();
      
      public ClassB create(){
          return classB;
       }
      }
      
      **Option 2:**
      @Component
      public class ClassA {
      
      private ClassB classB = null;
      
      public ClassB create(){
          classB = new ClassB();
          return classB;
       }
      }
      
      **Option 3:**
      @Component
      public class ClassA {
      
      public ClassB create(){
          ClassB classB = new ClassB();
          return classB;
       }
      }
      

      【讨论】:

      • 我认为这更适合作为一个新问题或评论:)
      猜你喜欢
      • 2012-04-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-30
      • 2021-09-23
      • 1970-01-01
      相关资源
      最近更新 更多