多线程Java单例不断重置
Multithreaded Java singleton keeps resetting
我目前正在 "Head First Design Patterns" 书第 175 页 "The Chocolate Factory" 中做一个 Java 练习,为了检验单例线程安全的理论,我实现了自己的多线程驱动程序 class.
练习说明:创建一个带有 2 个布尔变量的单例巧克力锅炉:empty 和 boiled,默认状态为 empty=true 和 boiled=false。以及写入变量的三个方法:fill()、drain() 和 boil()。
然而,当读取或写入变量 "empty" 和 "boiled" 时会出现问题。在线程 #1 填充 ChocolateBoiler 后,它设置 empty=false。然后线程 #2 启动,它说空被设置为默认值 true。这怎么可能?线程 #1 没有更新它吗?或者可能输出不正确,但更改已传播?我已经在所有访问器方法上配置了双重检查锁定,并且变量设置为 static volatile。
我已附加以下代码:
Client.Java
package creational.singleton.chocolatefactory;
public class Client extends Thread{
public void run() {
ChocolateBoiler boiler = ChocolateBoiler.getInstance();
System.out.println("new Boiler " + Thread.currentThread().getId() + "= isBoiled: " + boiler.isBoiled() + ", isEmpty: " + boiler.isEmpty());
boiler.fill();
System.out.println("filled Boiler " + Thread.currentThread().getId() + "= isBoiled: " + boiler.isBoiled() + ", isEmpty: " + boiler.isEmpty());
boiler.boil();
System.out.println("boiled Boiler " + Thread.currentThread().getId() + "= isBoiled: " + boiler.isBoiled() + ", isEmpty: " + boiler.isEmpty());
}
public static void main(String[] args) {
Client obj = new Client();
Thread t1 = new Thread(obj);
Thread t2 = new Thread(obj);
Thread t3 = new Thread(obj);
t1.start();
t2.start();
t3.start();
}
}
和
ChocolateBoiler.java
package creational.singleton.chocolatefactory;
public class ChocolateBoiler {
//volatile guarantees visibility of changes to variables across threads
//eager initialization for better thread safety
private volatile static ChocolateBoiler uniqueInstance = new ChocolateBoiler();
private volatile static boolean empty = true;
private volatile static boolean boiled = false;
private ChocolateBoiler() {}
public static ChocolateBoiler getInstance() {
return uniqueInstance;
}
public void fill() {
if (isEmpty()) {
synchronized(uniqueInstance){
if (isEmpty()) {
ChocolateBoiler.empty = false;
ChocolateBoiler.boiled = false;
}
}
}
}
public void drain(){
if (!isEmpty() && isBoiled()) {
synchronized(uniqueInstance){
if (!isEmpty() && isBoiled()) {
ChocolateBoiler.empty = true;
}
}
}
}
public void boil(){
if (!isEmpty() && !isBoiled()) {
synchronized(uniqueInstance){
if (!isEmpty() && !isBoiled()) {
ChocolateBoiler.boiled = true;
}
}
}
}
public boolean isEmpty() {
synchronized(uniqueInstance){
return ChocolateBoiler.empty;
}
}
public boolean isBoiled() {
synchronized(uniqueInstance){
return ChocolateBoiler.boiled;
}
}
}
输出结果如下:
new Boiler 20= isBoiled: false, isEmpty: true
filled Boiler 20= isBoiled: false, isEmpty: false
new Boiler 19= isBoiled: false, isEmpty: true
注意最后一行说:isEmpty: true
它不应该说:isEmpty: false
========================================
= 解决方案
问题出在 Client.java
package creational.singleton.chocolatefactory;
public class Client extends Thread{
ChocolateBoiler boiler = ChocolateBoiler.getInstance();
public void run() {
printState("new");
boiler.fill();
printState("filled");
boiler.boil();
printState("boiled");
}
public synchronized void printState(String state){
System.out.println(state + " Boiler " + Thread.currentThread().getId() + "= isBoiled: " + boiler.isBoiled() + ", isEmpty: " + boiler.isEmpty());
}
public static void main(String[] args) {
Client obj = new Client();
Thread t1 = new Thread(obj);
Thread t2 = new Thread(obj);
Thread t3 = new Thread(obj);
t1.start();
t2.start();
t3.start();
}
}
现在输出如下:
new Boiler 19= isBoiled: false, isEmpty: true
filled Boiler 19= isBoiled: false, isEmpty: false
new Boiler 20= isBoiled: true, isEmpty: false
new Boiler 21= isBoiled: true, isEmpty: false
filled Boiler 21= isBoiled: true, isEmpty: false
filled Boiler 20= isBoiled: true, isEmpty: false
boiled Boiler 19= isBoiled: true, isEmpty: false
boiled Boiler 20= isBoiled: true, isEmpty: false
boiled Boiler 21= isBoiled: true, isEmpty: false
虽然各个方法是同步的,但行 System.out.println("boiled Boiler " + Thread.currentThread().getId() + "= isBoiled: " + boiler.isBoiled() + ", isEmpty: " + boiler.isEmpty());进行两个单独的方法调用,并且在并发上下文中,无法保证任何事情。
也尝试在同步块中调用 System.out.println。
我目前正在 "Head First Design Patterns" 书第 175 页 "The Chocolate Factory" 中做一个 Java 练习,为了检验单例线程安全的理论,我实现了自己的多线程驱动程序 class.
练习说明:创建一个带有 2 个布尔变量的单例巧克力锅炉:empty 和 boiled,默认状态为 empty=true 和 boiled=false。以及写入变量的三个方法:fill()、drain() 和 boil()。
然而,当读取或写入变量 "empty" 和 "boiled" 时会出现问题。在线程 #1 填充 ChocolateBoiler 后,它设置 empty=false。然后线程 #2 启动,它说空被设置为默认值 true。这怎么可能?线程 #1 没有更新它吗?或者可能输出不正确,但更改已传播?我已经在所有访问器方法上配置了双重检查锁定,并且变量设置为 static volatile。
我已附加以下代码:
Client.Java
package creational.singleton.chocolatefactory;
public class Client extends Thread{
public void run() {
ChocolateBoiler boiler = ChocolateBoiler.getInstance();
System.out.println("new Boiler " + Thread.currentThread().getId() + "= isBoiled: " + boiler.isBoiled() + ", isEmpty: " + boiler.isEmpty());
boiler.fill();
System.out.println("filled Boiler " + Thread.currentThread().getId() + "= isBoiled: " + boiler.isBoiled() + ", isEmpty: " + boiler.isEmpty());
boiler.boil();
System.out.println("boiled Boiler " + Thread.currentThread().getId() + "= isBoiled: " + boiler.isBoiled() + ", isEmpty: " + boiler.isEmpty());
}
public static void main(String[] args) {
Client obj = new Client();
Thread t1 = new Thread(obj);
Thread t2 = new Thread(obj);
Thread t3 = new Thread(obj);
t1.start();
t2.start();
t3.start();
}
}
和 ChocolateBoiler.java
package creational.singleton.chocolatefactory;
public class ChocolateBoiler {
//volatile guarantees visibility of changes to variables across threads
//eager initialization for better thread safety
private volatile static ChocolateBoiler uniqueInstance = new ChocolateBoiler();
private volatile static boolean empty = true;
private volatile static boolean boiled = false;
private ChocolateBoiler() {}
public static ChocolateBoiler getInstance() {
return uniqueInstance;
}
public void fill() {
if (isEmpty()) {
synchronized(uniqueInstance){
if (isEmpty()) {
ChocolateBoiler.empty = false;
ChocolateBoiler.boiled = false;
}
}
}
}
public void drain(){
if (!isEmpty() && isBoiled()) {
synchronized(uniqueInstance){
if (!isEmpty() && isBoiled()) {
ChocolateBoiler.empty = true;
}
}
}
}
public void boil(){
if (!isEmpty() && !isBoiled()) {
synchronized(uniqueInstance){
if (!isEmpty() && !isBoiled()) {
ChocolateBoiler.boiled = true;
}
}
}
}
public boolean isEmpty() {
synchronized(uniqueInstance){
return ChocolateBoiler.empty;
}
}
public boolean isBoiled() {
synchronized(uniqueInstance){
return ChocolateBoiler.boiled;
}
}
}
输出结果如下:
new Boiler 20= isBoiled: false, isEmpty: true
filled Boiler 20= isBoiled: false, isEmpty: false
new Boiler 19= isBoiled: false, isEmpty: true
注意最后一行说:isEmpty: true
它不应该说:isEmpty: false
========================================
= 解决方案
问题出在 Client.java
package creational.singleton.chocolatefactory;
public class Client extends Thread{
ChocolateBoiler boiler = ChocolateBoiler.getInstance();
public void run() {
printState("new");
boiler.fill();
printState("filled");
boiler.boil();
printState("boiled");
}
public synchronized void printState(String state){
System.out.println(state + " Boiler " + Thread.currentThread().getId() + "= isBoiled: " + boiler.isBoiled() + ", isEmpty: " + boiler.isEmpty());
}
public static void main(String[] args) {
Client obj = new Client();
Thread t1 = new Thread(obj);
Thread t2 = new Thread(obj);
Thread t3 = new Thread(obj);
t1.start();
t2.start();
t3.start();
}
}
现在输出如下:
new Boiler 19= isBoiled: false, isEmpty: true
filled Boiler 19= isBoiled: false, isEmpty: false
new Boiler 20= isBoiled: true, isEmpty: false
new Boiler 21= isBoiled: true, isEmpty: false
filled Boiler 21= isBoiled: true, isEmpty: false
filled Boiler 20= isBoiled: true, isEmpty: false
boiled Boiler 19= isBoiled: true, isEmpty: false
boiled Boiler 20= isBoiled: true, isEmpty: false
boiled Boiler 21= isBoiled: true, isEmpty: false
虽然各个方法是同步的,但行 System.out.println("boiled Boiler " + Thread.currentThread().getId() + "= isBoiled: " + boiler.isBoiled() + ", isEmpty: " + boiler.isEmpty());进行两个单独的方法调用,并且在并发上下文中,无法保证任何事情。
也尝试在同步块中调用 System.out.println。