【问题标题】:in java concurrent programming, is need to using synchronized when read value?在java并发编程中,读取值时是否需要使用同步?
【发布时间】:2016-05-19 01:47:12
【问题描述】:

这个类可以在多线程中使用,因为它是线程安全的。

public class Hello {
    private int value = 0;

    public synchronized int get() {
        return value;
    }

    public synchronized void set(int value) {
        this.value = value;
    }
}

我知道除了set()之外,get()时我们必须使用同步的原因是内存可见性。

java volatile 关键字可用于内存可见性。

那么......这个类也是线程安全的??

public class Hello {
    private volatile int value = 0;

    public int get() {
        return value;
    }

    public synchronized void set(int value) {
        this.value = value;
    }
}

【问题讨论】:

  • 用AtomicInteger怎么样
  • 顺便说一句,我认为你的第一个类不一定是线程安全的,你应该锁定一个外部对象
  • 在这种特定情况下,我相信synchronized 不是必需的:1. Java 中int 的读/写保证是原子的(不适用于长/双/引用)它们是您访问value 的唯一两种方式。 2. volatile 保证发生之前(这是 synchronized 的另一个原因)
  • 我们可以使用 AtomicInteger 但这不是这个问题的重点,我只想知道 volatile 可以在读取方法时使用,而不是同步。
  • 标题中的问题在问题正文中得到了回答。您的真正问题是关于volatile。即使没有同步,您的第二个示例也是线程安全的。 volatile 这样做。

标签: java java.util.concurrent concurrent-programming


【解决方案1】:

在您的具体示例中,您不需要额外的同步。鉴于您已经提到了内存可见性(又名发生在之前),我不会更深入地解释这一点。

但是,它并不普遍适用。您的示例中有几个假设足以简单地使用volatile

  1. value 的类型

    虽然您只是对value 进行简单的检索/分配,但并不总是保证对所有数据类型都是原子的。 Iirc,Java 仅保证此类操作对于 int 和小于 int 的类型是原子的。这意味着,例如,如果valuelong 类型,即使您已使用volatile 声明它,您仍可能在上面的示例中破坏value

  2. value上的操作

    好的,假设它是int。在您的示例中,您只是获取和分配一个int,其操作是原子的,因此只需使用volatile 就足够了。但是,如果您有另一种方法来执行类似value++ 的操作,则使用volatile 是不够的。在这种情况下,您可以考虑使用Atomic* 或使用synchronized


更新:我后来发现 JLS https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.7 提到使用 volatile 会在 double/long 上强制执行原子读/写。所以我原来的第1点实际上是错误的

【讨论】:

  • 谢谢,答案 2 就是我想知道的。只需分配 set() 方法会使问题混淆。反正你说如果value++这样的方法存在,get()方法也需要同步。
【解决方案2】:

在您的示例中,在 get()set 方法中都不需要使用 synchronized,因为 value 属性是使用 volatile 关键字声明的。

这是因为volatile 关键字在写入者线程和读取者线程之间强制happens-before 关系。

Java Language Specification, Section 8.3.1.4. volatile fields:

Java 编程语言允许线程访问共享变量(第 17.1 节)。作为一项规则,为了确保共享变量始终如一且可靠地更新,线程应该通过获取一个锁来确保它对这些变量具有独占性,该锁通常会强制这些共享变量互斥。

Java 编程语言提供了第二种机制,即 volatile 字段,在某些情况下它比锁定更方便。

可以将字段声明为 volatile,在这种情况下,Java 内存模型确保所有线程都看到变量的一致值(第 17.4 节)。

因此,在您的情况下,无需同步 get()set() 方法。

【讨论】:

    【解决方案3】:

    在您的班级Hello 中只有一个字段int value。同步锁定整个对象,因此操作繁重,您可以使用 AtomicInteger。由于 Atomic* 变量更快。 Volatile 只是满足“发生在”您无法实现操作的原子性,例如使用 volatile 进行“检查然后采取行动”。

    使Hello类线程安全,最好的办法

    import java.util.concurrent.atomic.AtomicInteger;
    
    public class Hello {
        private AtomicInteger value;
    
        public int get() {
            return this.value.get();
        }
    
        public synchronized void set(int value) {
            this.value.set(value);
        }
    }
    

    this post中提到的

    如果某个对象只有一个字段或其关键更新是 仅限于对象的一个​​字段,因此而不是使用 同步或其他线程安全的集合,原子变量 (AtlomicInteger、AtomicReference 等)都可以使用。

    【讨论】:

    • 非阻塞并不总是比阻塞好。 Synchronized 使用锁,AtomicInteger 也是如此(汇编指令 - lock cmpxchg
    • 嗨。是的 AtomicInteger 也使用锁,但在非常小的级别。像同步内部使用监视器,它锁定整个对象。从这个意义上说,AtomicInteger 比同步更好。
    • 所以你可能想重新审视这个声明Since Atomic* are lock free thus more efficient.
    • 感谢您的澄清。如果您可以尝试改进答案,那就太好了。如果你愿意。
    • 有一件事是 Atomic* 本身不是无锁的,但如果使用它们,它们会使我们的代码无锁。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多