概述

在本教程中,我们将学习volatile关键字与原子类的区别,以及它们解决的问题。首先,我们需要了解Java如何处理线程间的通信,并了解可能出现的意外问题(请参阅Java常见并发陷阱)。

线程安全是理解多线程应用程序内部工作的重要话题。我们也会讨论竞态条件,但不会深入探讨。

2. 并发问题

让我们通过一个简单的例子来观察原子类与volatile关键字的区别。假设我们要创建一个计数器,它将在多线程环境中工作。

理论上,任何应用线程都可以增加这个计数器的值。让我们用一种朴素的方法开始实现,并查看会出现什么问题:

public class UnsafeCounter {
    
    private int counter;
    
    int getValue() {
        return counter;
    }
    
    void increment() {
        counter++;
    }
}

这是一个完全工作的计数器,但不幸的是,仅适用于单线程应用。这种做法在多线程环境中会遇到可见性和同步问题。在大型应用中,这可能会导致难以追踪bug,甚至损坏用户数据。

3. 可见性问题

在多线程应用中,可见性问题是其中一个挑战。这个问题与Java的内存模型密切相关(请参阅java-volatile#共享多处理器架构)。

在多线程应用中,每个线程都有其对共享资源的缓存版本,并根据事件或调度更新主内存中的值。

线程缓存和主内存中的值可能不同。因此,即使一个线程在主内存中更新了值,这些更改也不会立即对其他线程可见。这就是可见性问题。

volatile关键字通过绕过本地线程的缓存来帮助我们解决这个问题。这样,volatile变量对所有线程都是可见的,所有线程都会看到相同的值。因此,当一个线程更新值时,所有线程都会看到新值。我们可以将其视为低级观察者模式,可以重写之前的实现:

public class UnsafeVolatileCounter {
    
    private volatile int counter;
    
    public int getValue() {
        return counter;
    }
    
    public void increment() {
        counter++;
    }
}

上述示例改进了计数器并解决了可见性问题。然而,我们仍然存在同步问题,我们的计数器在多线程环境中可能无法正确工作。

4. 同步问题

虽然volatile关键字有助于解决可见性问题,但我们仍然面临另一个问题。在我们的递增示例中,我们对变量count执行了两次操作。首先,我们读取该变量,然后为其分配新值。这意味着递增操作是非原子的。

**我们面临的是一个竞态条件**。每个线程应先读取值,然后递增,最后再写回。当多个线程开始使用该值并在另一线程写入之前读取时,问题就会出现。

这样,一个线程可能会覆盖另一个线程写入的结果。synchronized关键字可以解决这个问题。但是,这种方法可能会造成瓶颈,不是解决这个问题最优雅的解决方案。

5. 原子值

原子值提供了处理此类问题的更好、更直观的方式。它们的接口允许我们在没有同步问题的情况下与值进行交互和更新。

在内部,原子类确保在这种情况下,递增将是一个原子操作。因此,我们可以使用它来创建线程安全的实现:

public class SafeAtomicCounter {
    private final AtomicInteger counter = new AtomicInteger(0);
    
    public int getValue() {
        return counter.get();
    }
    
    public void increment() {
        counter.incrementAndGet();
    }
}

我们的最终实现是线程安全的,可以在多线程应用中使用。它与我们的第一个示例没有太大区别,只有通过使用原子类,我们才能在多线程代码中解决可见性和同步问题。

6. 总结

在本文中,我们了解到在多线程环境中工作时必须非常谨慎。这些问题可能很难追踪,而且在调试时可能不会出现。因此,了解Java如何处理这种情况至关重要。

volatile关键字可以帮助解决可见性问题,并解决内在原子操作的问题。设置标志是volatile关键字可能有用的例子之一。

原子变量有助于处理非原子操作,如递增-递减,或者任何需要在分配新值之前读取值的操作。原子值是解决代码中同步问题的一种简单而方便的方式。

如往常一样,示例代码可在GitHub上找到。


» 下一篇: Java Weekly, 第449期