>我有一个简单的类,我想使用原子布尔值测试线程安全。 如果测试的线程超过 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();
}
}
}
您没有正确完成锁定。您确保只有 1 个线程访问 initialized
变量(因为它是原子布尔值),但一旦进行了检查,就不会再进行同步。
目前,线程 T1 进入,检查 initialized
的值并将其设置为那里的第一个。线程 T2 可能会出现,检查 initialized
的值,不要输入 if 块。但是,此时无法保证lazyObject
已初始化。因此,当 T1 处于睡眠状态时(因此尚未设置lazyObject
),T2 从该方法返回并尝试打印lazyObject
。
解决此问题的最简单方法是,不要使用原子布尔值,使方法同步并使字段不稳定。无论在何处设置值,都应在同步块中执行此操作。
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();
}
}
....
}
或者,您可以在施工时设置值并使其成为最终值。如果所有字段都是不可变的,它将是线程安全的。
好吧,你需要使initialize
操作原子化(执行初始化逻辑并通过单个线程创建新对象),以便其他线程可以看到它的结果。
为此,例如,您可以使用synchronized
阻止其他人建议的方式,而不是原子布尔值。您也可以为此使用锁。
希望有帮助。
问题是当另一个线程当前正在初始化对象时,initialize()
方法将立即返回(因为compareAndSet 将返回 false)。因此,对于某些线程,在执行 initialize
方法后不会初始化对象。
单线程中运行正常,因为您创建了使用 false
初始化的AtomicBoolean
实例,然后在 if 语句中首次调用compareAndSet(false, true)
它将返回 true。根据文档,第一个参数是您期望的,第二个参数是更新值:
true if successful. False return indicates that the actual value was not equal to the expected value.
因此,如果第一个线程调用compareAndSet
则条件将为真,它将进入成功块并进入睡眠状态。现在第二个线程将调用compareAndSet
并且条件将为假,它将退出函数并在 null 上调用.toString
。因为您在第一个线程进入睡眠状态的成功块中创建实例。
您的线程同时执行initialize
,有些线程跳过包含 init 代码的 if 测试,因为initialized
为 true。
您可以围绕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();
}
}
}