构建器模式的线程安全实现

时间:2016-05-23 19:41:27

标签: java multithreading design-patterns

我在寻找article的良好做法时遇到了builder pattern

在文章中,作者提到引起我注意的事情。该模式是线程安全的。

build()方法的第一个变体是线程安全的:

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

尽管如此,我认为更好的方法是在设置者中抛出IllegalStateException

public User build() {
    User u = null;
    try {
        u = new User(this);
    }
    catch(IllegalStateException e){
        e.printStackTrace();
    }
    return u;
}

构造函数如下所示:

private User(UserBuilder builder)
{
    setAge(builder.age);
}

并且设置者是:

void setAge(int age) throws IllegalStateException {
    if(age > 0 && age < 120) this.age = age;
    else throw new IllegalStateException("Age out of range");
}

我的方法仍然是线程安全的吗?如果没有,为什么?以线程安全的方式实现构建器模式的最佳方法是什么?

2 个答案:

答案 0 :(得分:1)

您的提案是线程安全的,因为返回的User对象将具有合法范围内的值,但不能保证&#34;坏&#34;例。如果构建器具有非法值,您的示例会返回null而不是抛出异常,这可能是您想要的,也可能不是。

通常不希望从多个线程访问构建器对象。但是,使用线程安全代码总是更好,因为这样做很容易;我可以想象一个不寻常的情况,其中一个人实际上希望构建器由多个线程填充。当然,在这些情况下,需要正确地同步对构建器的访问,例如通过使用volatile变量。

答案 1 :(得分:1)

根据我的经验,没有简单的方法可以使构建器模式线程安全,假设它对大多数实现来说都是可能的。

构建器模式的本质意味着值应该在建立调用之间保持不变,直到调用构建方法并且可能超出。因此,共享构建器的任何线程都可能在调用构建方法之前更改值,从而导致除最后一个线程之外的所有线程的结果都不正确。

我无法确定您的解决方案最佳方法是什么,但我已经成功地在线程客户端中实现了构建器模式,方法是每次需要构建器时引入工厂来创建新构建器,并且仅将构建器用作局部变量这使它保持在一个单独的堆栈上。

我发现这是最简单,最安全,最有效的方法。

记住保持愚蠢的简单,如果你像你的例子一样跳过篮球,那么我会把它视为一种气味,并考虑一种新的方法。