有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java这个代码是如何线程安全的?

我在读一本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) 个答案

  1. # 1 楼答案

    正如@AshwineeKJha所观察到的,post正在解释“线程安全”的概念,即新的User对象的age属性是否一定会按预期进行验证。由于该属性基于final字段,因此可以确定,在对象完全构造后读取该值的线程将看到该属性的最终值。在构造之后验证新对象,而不是在构造之前验证生成器,从而避免了一种类型的线程安全问题

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

  2. # 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。在这种情况下,你不会得到任何异常,关于年龄的不变量被打破。这种情况在第一种情况下不会发生

  3. # 3 楼答案

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

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

    注意User(如果final类)是不可变的(因此是线程安全的),但是UserBuilder不是(因此是线程不安全的)。这是故意的。其思想是通过作为其生成器的线程不安全类来创建User类的一致实例。因此,在使用构建器之前,您必须小心,并且可能对在线程之间共享它们心存疑虑

    如果您必须在多个线程之间共享一个构建器,那么这里提出的建议可能是相关的。这项建议是以局部变量的堆栈限制为基础的。变量user是仅由创建它的线程看到的局部变量。这使得这种使用线程安全。第二个实现微妙地调用了UserBuilder本身的线程不安全性质,并导致了问题

    通常,最好只在build方法中返回一个新的User实例,并在构建器上提供的setters中保留单独的检查。是的,在线程之间明智地共享生成器实例。参见有效的Java项目2