为什么 Java Bean 模式不是线程安全的
WhyJava Bean Pattern is not threadsafe
Joshua Bloch 在 Effective Java,第 2 版中指出:
伸缩构造函数模式的一个替代方法是 JavaBean 模式,您可以在其中调用带有强制参数的构造函数,然后在以下之后调用任何可选的设置器:
Pizza pizza = new Pizza(12);
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);
这里的问题是,由于对象是通过多次调用创建的,因此在构建过程中可能处于不一致状态。这也需要付出很多额外的努力来保证线程安全。
我的问题:-
上面的代码不是线程安全的吗?我缺少任何基本的东西吗?
提前致谢,
苏里亚
作者如是说:
JavaBeans pattern precludes the possibility of making a class immutable and requires a added effort on the part of the programmer
to ensure thread safety.
我认为作者强调了这样一个事实,即提供防止对象不变性的方法是没有意义的,并且 可能 如果您将对象设计为不可变:一旦创建就永远不需要更改。
您的问题:
Why Java Bean Pattern is not threadsafe ?
任何提供改变字段方法的class都不是线程安全的。
这对于 JavaBeans 方法(通常不使用防御性副本)是正确的,但对于任何可变 class.
也是如此
如果您在线程之间没有竞争条件的上下文中使用它,那么操作非线程安全 class 并不是一个必要的问题。
例如这段代码是线程安全的:
Pizza pizza = new Pizza(12);
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);
因为 Pizza
实例没有声明为共享变量(实例或静态字段),而是在更受限的范围内声明和使用(可能是方法,但也可能是初始化程序)块)。
构建器模式提供了一种构建不可变对象的方法,因此根据定义是线程安全对象。
例如,使用构建器创建 Pizza
:
Pizza pizza = new Pizza.Builder().cheese(true).pepperoni(true).bacon(true).build();
只有对 build()
的调用才会创建和 returns Pizza
对象。
以前的调用操作一个 Builder
对象和 return 一个 Builder
.
所以,如果对象是不可变的,你不需要担心同步这些调用:
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);
因为不需要提供这些方法。所以他们不能被调用。
关于如何拥有线程安全的JavaBeans
如果您处于 Pizza
实例可以在多个线程之间共享的上下文中,则这些调用应该以同步方式完成:
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);
这些可以声明为 synchronized
方法和/或 Pizza
字段可以是可变的,但这些还不够。
的确,如果 Pizza 应该根据它自己的状态甚至根据另一个对象改变它的状态,我们也应该同步整个逻辑:检查直到 Pizza
的状态修改。
例如,假设 Pizza
必须添加一些意大利辣香肠一次:
代码可以是:
if (pizza.isWaitForPepperoni()){
pizza.addPepperoni(5);
}
这些语句不是原子的,因此不是线程安全的。
pizza.addPepperoni(5);
可以被两个并发线程调用,即使其中一个线程已经调用 pizza.addPepperoni(5);
.
所以我们应该确保没有其他线程调用 pizza.addPepperoni(5)
而它不应该调用(披萨会有太多意大利辣香肠)。
例如,通过在 Pizza
实例上执行同步语句:
synchronized(pizza){
if (pizza.isWaitForPepperoni()){
pizza.addPepperoni(5);
}
}
您向我们展示的代码仅涉及一个线程,因此此代码的线程安全性没有实际意义。
如果多个线程可以看到Pizza
实例,那么有两件事需要担心:
另一个线程能否在您完成初始化之前看到 Pizza
实例?
当另一个线程看到实例时,它会观察到正确的属性值吗?
第一个问题是在完成初始化之前不"publishing"引用另一个线程。
第二个问题可以通过使用适当的同步机制来解决,以确保更改可见。这可以通过多种方式完成。例如:
- 您可以将 getter 和 setter 声明为
synchronized
方法。
- 您可以将保存属性值的(私有)变量声明为
volatile
。
请注意,JavaBean 模式并未规定 bean 的构造方式。在您的示例中,您使用无参数构造函数,然后使用设置器设置字段。您还可以实现一个构造函数,它允许您传递为属性提供(非默认)初始值的参数。
This also requires a lot of extra effort to ensure thread safety
不是真的。在这种情况下,使 getter 和 setter 线程安全是一个小改动。例如:
public class Pizza {
private boolean cheese;
public synchronized /* added */ void setCheese(boolean cheese) {
this.cheese = cheese;
}
public synchronized /* added */ boolean isCheese() {
return cheese;
}
}
Joshua Bloch 在 Effective Java,第 2 版中指出:
伸缩构造函数模式的一个替代方法是 JavaBean 模式,您可以在其中调用带有强制参数的构造函数,然后在以下之后调用任何可选的设置器:
Pizza pizza = new Pizza(12);
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);
这里的问题是,由于对象是通过多次调用创建的,因此在构建过程中可能处于不一致状态。这也需要付出很多额外的努力来保证线程安全。
我的问题:- 上面的代码不是线程安全的吗?我缺少任何基本的东西吗?
提前致谢,
苏里亚
作者如是说:
JavaBeans pattern precludes the possibility of making a class immutable and requires a added effort on the part of the programmer to ensure thread safety.
我认为作者强调了这样一个事实,即提供防止对象不变性的方法是没有意义的,并且 可能 如果您将对象设计为不可变:一旦创建就永远不需要更改。
您的问题:
Why Java Bean Pattern is not threadsafe ?
任何提供改变字段方法的class都不是线程安全的。
这对于 JavaBeans 方法(通常不使用防御性副本)是正确的,但对于任何可变 class.
如果您在线程之间没有竞争条件的上下文中使用它,那么操作非线程安全 class 并不是一个必要的问题。
例如这段代码是线程安全的:
Pizza pizza = new Pizza(12);
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);
因为 Pizza
实例没有声明为共享变量(实例或静态字段),而是在更受限的范围内声明和使用(可能是方法,但也可能是初始化程序)块)。
构建器模式提供了一种构建不可变对象的方法,因此根据定义是线程安全对象。
例如,使用构建器创建 Pizza
:
Pizza pizza = new Pizza.Builder().cheese(true).pepperoni(true).bacon(true).build();
只有对 build()
的调用才会创建和 returns Pizza
对象。
以前的调用操作一个 Builder
对象和 return 一个 Builder
.
所以,如果对象是不可变的,你不需要担心同步这些调用:
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);
因为不需要提供这些方法。所以他们不能被调用。
关于如何拥有线程安全的JavaBeans
如果您处于 Pizza
实例可以在多个线程之间共享的上下文中,则这些调用应该以同步方式完成:
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);
这些可以声明为 synchronized
方法和/或 Pizza
字段可以是可变的,但这些还不够。
的确,如果 Pizza 应该根据它自己的状态甚至根据另一个对象改变它的状态,我们也应该同步整个逻辑:检查直到 Pizza
的状态修改。
例如,假设 Pizza
必须添加一些意大利辣香肠一次:
代码可以是:
if (pizza.isWaitForPepperoni()){
pizza.addPepperoni(5);
}
这些语句不是原子的,因此不是线程安全的。
pizza.addPepperoni(5);
可以被两个并发线程调用,即使其中一个线程已经调用 pizza.addPepperoni(5);
.
所以我们应该确保没有其他线程调用 pizza.addPepperoni(5)
而它不应该调用(披萨会有太多意大利辣香肠)。
例如,通过在 Pizza
实例上执行同步语句:
synchronized(pizza){
if (pizza.isWaitForPepperoni()){
pizza.addPepperoni(5);
}
}
您向我们展示的代码仅涉及一个线程,因此此代码的线程安全性没有实际意义。
如果多个线程可以看到Pizza
实例,那么有两件事需要担心:
另一个线程能否在您完成初始化之前看到
Pizza
实例?当另一个线程看到实例时,它会观察到正确的属性值吗?
第一个问题是在完成初始化之前不"publishing"引用另一个线程。
第二个问题可以通过使用适当的同步机制来解决,以确保更改可见。这可以通过多种方式完成。例如:
- 您可以将 getter 和 setter 声明为
synchronized
方法。 - 您可以将保存属性值的(私有)变量声明为
volatile
。
请注意,JavaBean 模式并未规定 bean 的构造方式。在您的示例中,您使用无参数构造函数,然后使用设置器设置字段。您还可以实现一个构造函数,它允许您传递为属性提供(非默认)初始值的参数。
This also requires a lot of extra effort to ensure thread safety
不是真的。在这种情况下,使 getter 和 setter 线程安全是一个小改动。例如:
public class Pizza {
private boolean cheese;
public synchronized /* added */ void setCheese(boolean cheese) {
this.cheese = cheese;
}
public synchronized /* added */ boolean isCheese() {
return cheese;
}
}