这段代码线程如何安全?

时间:2016-08-22 18:36:33

标签: java thread-safety

我正在阅读post on JavaCodeGeeks

public User build() {
    User user = new User(this);
    if (user.getAge() > 120) {
        throw new IllegalStateException(“Age out of range”); // thread-safe
    }
    return user;
}

public User build() {
    if (age > 120) {
        throw new IllegalStateException(“Age out of range”); // bad, not thread-safe
    }
    // This is the window of opportunity for a second thread to modify the value of age
    return new User(this);
}

我有一个问题,怎么能说前一个代码是线程安全的,而后一个代码不是。

3 个答案:

答案 0 :(得分:2)

如果两个线程使用相同的UserBuilder:

    User.UserBuilder builder = new User.UserBuilder("Jhon", "Doe");

    // First thread
    Thread t1 = new Thread(){
    @Override
    public void run()
    {
        builder.age(30).build();         
    }};

    // Second thread
    Thread t2 = new Thread(){
        @Override
        public void run()
        {
            // Just changing age temporarily in builder.
            builder.age(140);
            builder.age(35).build();         
        }};

   t1.start();
   t2.start();

使用build()的线程安全实现,您将永远不会有一个年龄大于120的用户,因为将抛出非法状态异常。

在第二种情况下,当t1检查条件age == 30但是new User(this)被称为age == 140时,可能会发生这种情况。在这种情况下,你不会得到任何例外,并且关于年龄的不变量会被打破。在第一种情况下不会发生这种情况。

答案 1 :(得分:2)

正如@AshwineeKJha所观察到的那样,该帖子正在根据新的User对象的age属性是否确定按预期进行验证来构建“线程安全”。因为该属性基于final字段,所以确定在完全构造对象之后读取值的线程将看到该属性的最终值。在构造之后验证新对象而不是在构造之前验证构建器因此避免了一种类型的线程安全问题。

另一方面,如果在线程之间共享构建器对象,则一个线程写入其age字段,而另一个线程读取其age字段,则需要两个线程之间的同步。在没有同步的情况下可以发生这种事件组合的程序未正确同步。关于类/方法的“线程安全”的通常定义是,类的用户不需要执行任何外部同步以确保在类之间共享实例时的正确同步。从这个意义上讲,所提供的代码都不是线程安全的。

答案 2 :(得分:0)

关于线程安全的评论使得事情不清楚。

这篇文章似乎暗示了在多个线程中共享builder的同一个实例(UserBuilder)的方向。无论如何,这不是一个好主意。构建模式的主要用途,如Effective Java Item 2中所述,仔细编码并演示此模式的使用,是为了避免在创建一致的不可变类实例时伸缩构造函数。

请注意User(如果final类)是不可变的(因此线程安全)但UserBuilder不是(因此线程不安全)。这是设计上的。我们的想法是通过作为构建器的线程不安全类来创建User类的一致实例。所以,在你使用构建器之前,你必须要小心,也许在线程之间共享它们可能是偏执狂。

如果你必须在多个线程中共享一个构建器,那么也许那里提出的建议是相关的。该建议捎带了局部变量的堆栈限制的想法。变量user是一个局部变量,仅由创建它的线程看到。这使得这种使用线程安全。第二个实现巧妙地调用UserBuilder本身的线程不安全性并导致麻烦。

通常,最好只在User方法中返回build的新实例,并将单独的检查留在构建器上提供的setters中。是的,明智地在线程之间共享构建器实例。请参阅Effective Java Item 2。