【问题标题】:Multithreaded Java singleton keeps resetting多线程Java单例不断重置
【发布时间】:2017-08-28 13:54:54
【问题描述】:

我目前正在“Head First Design Patterns”第 175 页“巧克力工厂”一书中进行 Java 练习,为了测试单例是线程安全的理论,我实现了自己的多线程驱动程序类。

练习状态:创建一个带有 2 个布尔变量的单例巧克力锅炉:空和煮沸,默认状态为空=真和煮沸=假。以及写入变量的三个方法:fill()、drain() 和 boil()。

但是,当读取或写入变量“empty”和“boiled”时会出现问题。在线程#1 填充 ChocolateBoiler 后,它设置 empty=false。然后线程#2 启动,它说空设置为其默认值true。这怎么可能?线程#1 没有更新吗?或者也许输出不正确,但变化传播了?我已在所有访问器方法上配置了双重检查锁定,并且变量设置为静态易失性。

我已附加以下代码:

客户端.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

【问题讨论】:

  • 这不是你得到的输出。您已经创建并启动了 3 个线程,因此您应该得到 9 行输出。
  • 正确,它是为了澄清而缩写的,运行程序时会得到: new Boiler 20= isBoiled: false, isEmpty: truefilled Boiler 20= isBoiled: false, isEmpty: false new Boiler 19= isBoiled: false, isEmpty: true 已满 Boiler 19= isBoiled: true, isEmpty: false boiler 19= isBoiled: true, isEmpty: false new Boiler 21= isBoiled: false, isEmpty: true满了 Boiler 21= isBoiled: true, isEmpty: false boiler 21= isBoiled: true, isEmpty: false boiler 20= isBoiled: true, isEmpty: false 问题依旧……

标签: java multithreading singleton reset


【解决方案1】:

虽然个别方法是同步的,但 System.out.println("boiled Boiler" + Thread.currentThread().getId() + "= isBoiled: " +boiler.isBoiled() + ", isEmpty: " +锅炉.isEmpty());进行两个单独的方法调用,并且在并发上下文中,什么都不能保证。

尝试在同步块中调用 System.out.println。

【讨论】:

  • 感谢修复它,将 run() 中的所有内容放在同步块中工作。它现在读作:new Boiler 21= isBoiled: true, isEmpty: false
  • @HughPearse,你不应该把 everything 放在同步块下,否则你将失去在多个线程中运行它的全部意义。关键是在每一步之后获得一致的对象状态。这正是 Imran 所说的——你应该在每一步之后同步对象状态的每个输出。
猜你喜欢
  • 1970-01-01
  • 2013-06-02
  • 1970-01-01
  • 2017-11-05
  • 2012-08-28
  • 1970-01-01
  • 2016-03-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多