当前位置: 首页 > 知识库问答 >
问题:

如何使用volatile变量编写简单的线程安全类?

郝玄天
2023-03-14

我想写一个简单的线程安全类,可以用来设置或获取整数值。

最简单的方法是使用synchronized关键字:

public class MyIntegerHolder {

    private Integer value;

    synchronized public Integer getValue() {
        return value;
    }

    synchronized public void setValue(Integer value) {
        this.value = value;
    }

}

我也可以尝试使用挥发性:

public class MyIntegerHolder {

    private volatile Integer value;

    public Integer getValue() {
        return value;
    }

    public void setValue(Integer value) {
        this.value = value;
    }

}

带有易失性关键字线程安全的类吗?

考虑下面的事件顺序:

  1. 线程A将该值设置为5

根据Java语言规范

  • “1”发生在“3”之前

但我不明白“1”怎么会出现在“2”之前,所以我怀疑“1”不会出现在“2”之前。

我怀疑线程C的读数可能是7或5。我认为带有volatile关键字的类不是线程安全的,以下顺序也是可能的:

  1. 线程A将该值设置为5

我假设MyIntegerHolder with volatile不是线程安全的,对吗?

是否可以使用AtomicInteger创建线程安全的整数保持器:

public class MyIntegerHolder {

    private AtomicInteger atomicInteger = new AtomicInteger();

    public Integer getValue() {
        return atomicInteger.get();
    }

    public void setValue(Integer value) {
        atomicInteger.set(value);
    }

}

以下是《Java并发性练习手册》的一部分:

“原子变量的读写与易失性变量具有相同的内存语义。”

编写线程安全MyIntegerHolder的最佳(最好是非阻塞)方式是什么?

如果你知道答案,我想知道你为什么认为它是正确的。它符合规格吗?如果是,怎么做?

共有3个答案

闻人宇定
2023-03-14

Java语言规范的第17章定义了内存操作(如读取和写入共享变量)的“发生在”关系。只有在写操作发生在读操作之前,一个线程的写操作的结果才能保证对另一个线程的读操作可见。

  1. 同步的和易失性的构造,以及Thread.start()和Thread.join()方法,可以形成发生前关系。特别是:线程中的每个操作都发生在该线程中按程序顺序稍后出现的每个操作之前。
  2. 监视器的解锁(同步块或方法退出)发生在同一监视器的每个后续锁(同步块或方法条目)之前。而且因为在发生之前发生的关系是可传递的,所以线程在解锁之前的所有动作都发生在监控的任何线程锁定之后的所有动作之前。
  3. 对易失性字段的写入发生在同一字段的每次后续读取之前。易失性字段的写入和读取与进入和退出监视器具有类似的内存一致性效果,但不需要互斥锁定。
  4. 在启动线程中的任何操作之前,会发生对线程启动的调用。
  5. 线程中的所有操作都发生在任何其他线程从该线程的连接成功返回之前。

参考:http://developer.android.com/reference/java/util/concurrent/package-summary.html

根据我的理解,3的意思是:如果你写(不是基于读取结果)/读取就可以了。如果写入(基于读取结果,例如增量)/读取不正常。因为volatile“不需要互斥锁定”

夏侯楷
2023-03-14

如果只需要对变量进行get/set,就可以像之前那样声明它为volatile。如果您检查AtomicInteger设置/获取的工作方式,您将看到相同的实现

private volatile int value;
...

public final int get() {
    return value;
}

public final void set(int newValue) {
    value = newValue;
}

但是你不能像这样简单地增加一个不稳定的场。这就是我们使用原子nteger.increment和获取或getAnd增量方法的地方。

屠和洽
2023-03-14

关键字synchronized表示如果线程A和线程B想要访问整数,它们不能同时访问。A告诉B等我做完。

另一方面,volatile使线程更“友好”。他们开始互相交谈,一起工作来完成任务。因此,当B试图访问时,A将通知B在那一刻之前所做的一切。B现在意识到了这些变化,可以从A离开的地方继续工作。

在Java中,由于这个原因,您有原子,它在封面下使用易失性关键字,所以它们几乎在做同样的事情,但是它们节省了您的时间和精力。

你要找的是AtomicInteger,你说得对。对于您尝试执行的操作,这是最佳选择。

There are two main uses of `AtomicInteger`:

 * As an atomic counter (incrementAndGet(), etc) that can be used by many threads concurrently

 * As a primitive that supports compare-and-swap instruction (compareAndSet()) to implement non-blocking algorithms. 

概括地回答你的问题

这取决于你需要什么。我不是说同步是错误的,易失性是好的,否则好Java的人早就删除了同步。没有绝对的答案,有很多具体的案例和使用场景。

我的一些书签:

并发提示

核心Java并发

Java并发

使现代化

Java并发规范如下:

包java。util。同时发生的原子的

一个支持单变量无锁线程安全编程的小类工具包。

Instances of classes `AtomicBoolean`, `AtomicInteger`, `AtomicLong`, and `AtomicReference` each provide access and updates to a single variable of the corresponding type.
Each class also provides appropriate utility methods for that type.
For example, classes `AtomicLong` and AtomicInteger provide atomic increment methods.

The memory effects for accesses and updates of atomics generally follow the rules for volatiles:

get has the memory effects of reading a volatile variable.
set has the memory effects of writing (assigning) a volatile variable.

也从这里开始

Java编程语言volatile关键字:

(在Java的所有版本中)对易失性变量的读取和写入有全局顺序。这意味着每个访问易失性字段的线程将在继续之前读取其当前值,而不是(潜在地)使用缓存值。(但是,不能保证易失性读写与常规读写的相对顺序,这意味着它通常不是一个有用的线程构造。)

 类似资料:
  • 问题内容: 我是Go的新手,我需要创建一个线程安全的变量。我知道在Java中您只能使用关键字,但是似乎没有类似的东西存在。有什么方法可以同步变量? 问题答案: Java中的一种意思是仅允许单个线程(在任何给定时间)执行代码块。 在Go中,有很多构造可以实现该目标(例如互斥体,通道,等待组,中的原语),但是Go的谚语是: “不要通过共享内存进行通信;而是通过通信来共享内存。” 因此,不要锁定和共享变

  • 我一直在浏览JCIP,作者说。。 线程限制的一种特殊情况适用于可变变量。对共享的易失性变量执行读-修改-写操作是安全的,只要您确保该易失性变量仅从单个线程写入 例如,实例计数被认为是一个复合操作(读取值,向其添加一个值,并更新值),声明计数为易失性不会使此操作原子化,因此线程安全在这里得不到保证!!我说得对吗??但是在这里,作者说如果我们确保只从单个线程写入易失性变量,我们就可以修复它。我不明白这

  • 类OneValueCache是不可变的。但是我们可以更改变量缓存的引用。 但我不能理解为什么VolateCachedFactorizer类是线程安全的。 对于两个线程(线程A和线程B),如果线程A和线程B同时到达,那么两个线程A和B都将尝试创建OnEvalueCache。然后线程A到达而线程B同时到达。然后线程A将创建一个,它覆盖threadB创建的值(OneValueChange是不可变的,但是

  • 问题内容: 我编写了一个单一的Kafka使用者(使用Spring Kafka),该使用者从单个主题中读取内容,并且是使用者组的一部分。消耗完一条消息后,它将执行所有下游操作,并移至下一个消息偏移。我将其打包为WAR文件,并且我的部署管道将其推送到单个实例。使用部署管道,我可以将该工件部署到部署池中的多个实例。 但是,当我希望多个消费者作为基础架构的一部分时,我无法理解以下内容: 实际上,我可以在部

  • 问题内容: 给出以下代码: 线程类将定期(通过执行器每5分钟更新一次)myConfigData成员变量。myConfigData的设置是否在“外部”线程中是线程安全的(原子的),还是我必须将每个读写操作都同步到myConfigData变量? 编辑:问题不是ConcurrentHashMap是否是线程安全的(根据Javadoc),而是ConcurrentHashMap本身在myConfigData

  • 我正在阅读JCIP,无法理解3.3.1中的以下语句, 对共享的易失性变量执行读-修改-写操作是安全的,只要可以确保仅从单个线程写入易失性变量。在本例中,您将修改限制在单个线程中,以防止出现争用情况,并且volatile变量的可见性保证确保其他线程看到最新的值。 即使挥发性变量只从单个线程写入,它怎么能不提高竞争条件?如果线程A在计数器为1时执行,线程B可以在计数器1写入内存之前进入,并获得的旧值1