我有一个简单的类,我想使用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();
}
}
}
答案 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
。因为您在第一个线程进入休眠状态的成功块中创建实例。