我在寻找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");
}
我的方法仍然是线程安全的吗?如果没有,为什么?以线程安全的方式实现构建器模式的最佳方法是什么?
答案 0 :(得分:1)
您的提案是线程安全的,因为返回的User
对象将具有合法范围内的值,但不能保证&#34;坏&#34;例。如果构建器具有非法值,您的示例会返回null而不是抛出异常,这可能是您想要的,也可能不是。
通常不希望从多个线程访问构建器对象。但是,使用线程安全代码总是更好,因为这样做很容易;我可以想象一个不寻常的情况,其中一个人实际上希望构建器由多个线程填充。当然,在这些情况下,需要正确地同步对构建器的访问,例如通过使用volatile变量。
答案 1 :(得分:1)
根据我的经验,没有简单的方法可以使构建器模式线程安全,假设它对大多数实现来说都是可能的。
构建器模式的本质意味着值应该在建立调用之间保持不变,直到调用构建方法并且可能超出。因此,共享构建器的任何线程都可能在调用构建方法之前更改值,从而导致除最后一个线程之外的所有线程的结果都不正确。
我无法确定您的解决方案最佳方法是什么,但我已经成功地在线程客户端中实现了构建器模式,方法是每次需要构建器时引入工厂来创建新构建器,并且仅将构建器用作局部变量这使它保持在一个单独的堆栈上。
我发现这是最简单,最安全,最有效的方法。
记住保持愚蠢的简单,如果你像你的例子一样跳过篮球,那么我会把它视为一种气味,并考虑一种新的方法。