AtomicBoolean是否保证“线程安全”?

5
根据我在互联网上阅读的一些文档,类似于AtomicIntegerAtomicLong等类的变量只允许同时有1个线程访问它们。但是当我尝试使用AtomicBoolean进行测试时,出现了问题。例如:
public class TestAtomicBoolean {
    public static void main(String[] args) {
        final AtomicBoolean atomicBoolean = new AtomicBoolean(false);

        new Thread("T1") {
            @Override
            public void run() {
                while (true) {
                    System.out.println(Thread.currentThread().getName() + " is waiting for T3 set Atomic to true. Current is " + atomicBoolean.get());
                    if (atomicBoolean.compareAndSet(true, false)) {                        
                        System.out.println("Done. Atomic now is " + atomicBoolean.get());
                        break;
                    }                    
                }
            }
        }.start();

        new Thread("T2") {
            @Override
            public void run() {
                while(true) {
                    System.out.println(Thread.currentThread().getName() + " " + atomicBoolean.get());                    
                }               
            }           
        }.start();

        new Thread("T3") {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " " + atomicBoolean.get());
                System.out.println(Thread.currentThread().getName() + " is setting atomic to true");
                atomicBoolean.set(true);
                System.out.println(Thread.currentThread().getName() + " " + atomicBoolean.get());
            }           
        }.start();                
    }
}

输出

T1 is waiting for T3 set Atomic to true. Current is false
T1 is waiting for T3 set Atomic to true. Current is false
T3 is setting atomic to true
T2 false
T3 true (*)
T1 is waiting for T3 set Atomic to true. Current is false (*)
T2 true
Done. Atomic now is false
T2 false

在第二行 (*), 虽然 T3 将 AtomicBoolean 设为 true,但之后 T1 读取的值是 false。所以,T1 和 T3 在同一时间访问了 AtomicBoolean?我不理解 AtomicBoolean 的工作原理。
请有经验的人帮忙解答一下吗?

@ElliottFrisch,你能再清楚地解释一下吗?:( - Qk Lahpita
没有什么神奇的方法可以保证线程安全。即使您的代码执行的每个操作都是单独的线程安全,也不一定会使整个程序变得线程安全。 - Solomon Slow
2个回答

8

AtomicBoolean是绝对原子且线程安全的。

但在您的示例中,您尝试测试AtomicBoolean的原子性依赖于System.out.println打印日志的顺序,这是具有误导性的。

因此,如果我们查看System.out.println()代码:

public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

我们将在上面的println()方法的上下文中查看事件流。
简短回答:
Thread T1打印 -> 等待T3将Atomic设置为true。当前为false Thread T1打印 -> 等待T3将Atomic设置为true。当前为false Thread T3打印 -> T3正在将原子置为true Thread T1调用sysout以打印 -> 等待T3将Atomic设置为true。当前为false(刚调用了sysout方法但尚未获得锁定) Thread T3打印 -> T3 true的打印 Thread T1 sysout完成并打印 -> 等待T3将Atomic设置为true。当前为false 日志的顺序给人一种印象,即T1没有读取atomicBoolean的当前值,而这是由于在线程进行System.out.println时可能发生的交错导致的。
详细顺序:
应用程序从false的初始值开始,对于atomicBoolean
final AtomicBoolean atomicBoolean = new AtomicBoolean(false);

您好,以下是翻译内容:

您输出的初始两个日志来自T1,按预期执行并打印出atomicBoolean的值为false。现在,我们将忽略T2以简化操作,因为即使只有两个线程,我们也可以看到流程。

现在,T3开始执行,并且即将使atomicBoolean变为true,如输出所示。

T3 is setting atomic to true

在打印上述行之后,T1立即获得执行的机会。此时atomicBoolean的值为false。因此JVM创建了字符串T1 is waiting for T3 set Atomic to true. Current is false并在调用或刚进入System.out.println方法时,但尚未到达synchronized(this)语句,因此this上尚未获得锁定。

此时,T3可能已经轮到它继续执行,并使atomicBoolean变为true,并使用System.out.println()打印了T3 true,即获取并释放锁(在this上)。

现在,T1从其上次离开的位置恢复执行,即System.out.println。但请记住,它要打印的字符串的值已经构建好了,其值为T1 is waiting for T3 set Atomic to true. Current is false。因此,现在T1打印此行并继续执行。

有效地按照此流程,日志将如您所观察到的那样。
T3 true
T1 is waiting for T3 set Atomic to true. Current is false

图示说明

下面是关于T1和T3的流程,并尝试捕捉上述讨论。 ---- 表示该线程正在执行。空格表示它正在等待轮到它。

    1(false) 1(false)           1(false)just invoked   1(false)completed
T1 -------------------          ------                ------------------
T3                    ----------      ----------------  
                       2(false)        3(true)

LEGEND:
 1(false) - printing of T1 is waiting for T3 set Atomic to true. Current is false
 2(false) - printing of T3 is setting atomic to true
 3(true) - printing of T3 true

使用一个日志框架log4j进行多线程编程 http://logging.apache.org/log4j/2.x/ - mubeen
@mubeen 说实话,我不是百分之百确定我们的代码中是否已经存在这样的日志记录或者能否实现。我能想到的一个选项是编写一个实用类,并拥有一个static synchronized方法,在内部调用sysout()。但我确实感觉并且几乎可以看到仍然存在一个小窗口,可以发生上述流程。 - Madhusudana Reddy Sunnapu
我明白了。非常感谢! - Qk Lahpita

0

我已经阅读了官方文档,但仍然不理解 :( "AtomicBoolean: 可以原子性地更新的布尔值。" 在我的例子中,它并不是“原子性”的 :s 两个线程可以同时访问它 :s - Qk Lahpita

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接