我的线程安全类使用AtomicBoolean有什么问题

时间:2013-12-30 22:42:26

标签: java thread-safety

我有一个简单的类,我想使用AtomicBoolean测试线程安全。如果测试超过2个线程,它不起作用。它在某些线程中在doSome方法中抛出NullPointerExcetion:有人能告诉我我错了吗?感谢。

package jcafe.common.utils;

import java.util.concurrent.atomic.AtomicBoolean;

public class ThreadSafeTest {

    private final AtomicBoolean initialized = new AtomicBoolean(false);

    private Object lazyObject = null;

    protected void initialize() {
        if (initialized.compareAndSet(false, true)) {

            // Some other expensive init here
            // Some other expensive init here

            this.lazyObject = new Object();
        }
    }

    public void doSome() {
        initialize();

        // NullPointerException here: this.lazyObject = null
        System.out.println(this.lazyObject.toString());
    }

    public static void main(String[] args) {
        final ThreadSafeTest test = new ThreadSafeTest ();

        for (int i = 0; i < 100; i++) {
            Thread t = new Thread(new Runnable() {

                @Override
                public void run() {
                    test.doSome();
                }
            });
            t.start();
        }
    }
}

5 个答案:

答案 0 :(得分:2)

您尚未正确完成锁定。 您确保只有1个线程正在访问initialized变量(因为它是AtomicBoolean),但是一旦发生该检查,就不再进行同步。

当线程T1进入时,检查initialized的值并将其设置为第一个那里。线程T2可以出现,检查initialized的值,而不是输入if块。但是,目前无法保证lazyObject已初始化。因此,当T1处于睡眠状态时(因此lazyObject尚未设置),T2将从该方法返回并尝试打印lazyObject

解决这个问题的最简单方法是,不要使用AtomicBoolean,使方法同步并使字段变为volatile。无论您在何处设置值,都应该在同步块中执行此操作。

public class ThreadSafeTest {

    private volatile boolean initialized = false;

    private volatile Object lazyObject = null;

    protected synchronized void initialize() {
        if (!initialized) {
            initialized = true;                
            // Some other expensive init here
            this.lazyObject = new Object();
        }
    }
    ....
}

或者,您可以在施工时设置值并使其成为最终值。如果所有字段都是不可变的,那么它将是线程安全的。

答案 1 :(得分:2)

好吧,你需要使initialize操作原子(执行init逻辑并通过单个线程创建新对象),以便其他线程可以看到它的结果。

为此,您可以使用synchronized阻止其他人建议的方式,而不是原子布尔值。您也可以使用Lock

希望有所帮助。

答案 2 :(得分:1)

问题是当另一个线程当前正在初始化对象时,initialize()方法将立即返回(因为compareAndSet将返回false)。因此,对于某些线程,在执行initialize方法后,对象不会被初始化。

答案 3 :(得分:1)

您的线程正在同时执行initialize,有些正在跳过包含init代码的if测试,因为initialized为真。

您可以围绕initialized变量添加同步

protected void initialize() {
    synchronized(initialized) {
        if (initialized.compareAndSet(false, true)) {
            try {
                // Some other expensive init here
                Thread.sleep(2 * 1000);
            } catch (InterruptedException e) {
            }
            this.lazyObject = new Object();
        }
    }
}

答案 4 :(得分:1)

它在单线程中运行正常,因为你创建了AtomicBoolean的实例,用false初始化,然后在if语句中调用compareAndSet(false, true)进行第一次调用它将返回true。根据文档,第一个参数是你期望的,第二个是更新值:

true if successful. False return indicates that the actual value was not equal to the expected value.

因此,如果第一个线程调用compareAndSet,则条件为真,它将进​​入成功块并进入休眠状态。现在第二个线程将调用compareAndSet,因为它的条件将为false,它将退出函数并在null上调用.toString。因为您在第一个线程进入休眠状态的成功块中创建实例。