拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 Java 中的易失变量与原子变量

Java 中的易失变量与原子变量

白鹭 - 2022-08-08 2268 0 2

一、概述

在本教程中,我们将了解volatile关键字和原子类之间的区别以及它们解决的问题。首先,有必要了解Java 如何处理线程之间的通信以及可能出现哪些意外问题。

线程安全是一个至关重要的话题,它可以深入了解多线程应用程序的内部工作。我们还将讨论竞争条件,但我们不会深入探讨这个主题。

2.并发问题

我们通过一个简单的例子来看看原子类和volatile关键字的区别。想像一下,我们正在尝试创建一个将在多线程环境中工作的计数器。

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

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

这是一个完美工作的计数器,但不幸的是,它仅适用于单线程应用程序。这种方法在多线程环境中会遇到可见性和同步问题。在大型应用程序中,跟踪错误甚至损坏用户数据可能会造成困难。

3.能见度问题

可见性问题是在多线程应用程序中工作时的问题之一。可见性问题与Java 内存模型紧密相关。

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

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

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();
}
}

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

六,结论

在本文中,我们了解到在多线程环境中工作时应该非常谨慎。错误和问题很难追踪,并且在调试时可能不会出现。这就是为什么必须了解Java 如何处理这些情况的原因。

volatile关键字可以帮助解决可见性问题并通过本质上的原子操作解决问题。设置标志是volatile关键字可能有用的示例之一。

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


标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *