如果不会发生争用,则读取时是否需要同步

发布于 2024-10-05 03:42:26 字数 502 浏览 0 评论 0 原文

考虑下面的代码狙击:

package sync;

public class LockQuestion {
    private String mutable;

    public synchronized void setMutable(String mutable) {
        this.mutable = mutable;
    }

    public String getMutable() {
        return mutable;
    }   
}

在 Time1 线程 Thread1 将更新“可变”变量。 setter 中需要同步,以便将内存从本地缓存刷新到主内存。 在时间 Time2(Time2 > Time1,无线程争用)线程 Thread2 将读取可变值。

问题是 – 我需要将同步放在 getter 之前吗?看起来这不会导致任何问题 - 内存应该是最新的,并且 Thread2 的本地缓存内存应该由 Thread1 失效并更新,但我不确定。

Consider code sniper below:

package sync;

public class LockQuestion {
    private String mutable;

    public synchronized void setMutable(String mutable) {
        this.mutable = mutable;
    }

    public String getMutable() {
        return mutable;
    }   
}

At time Time1 thread Thread1 will update ‘mutable’ variable. Synchronization is needed in setter in order to flush memory from local cache to main memory.
At time Time2 ( Time2 > Time1, no thread contention) thread Thread2 will read value of mutable.

Question is – do I need to put synchronized before getter? Looks like this won’t cause any issues - memory should be up to date and Thread2’s local cache memory should be invalidated&updated by Thread1, but I’m not sure.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

甜妞爱困 2024-10-12 03:42:26

与其想知道,为什么不直接使用 java.util.concurrent

(就其价值而言,我阅读了 happens-before 不保证 Thread2 会看到可变的更改,除非它也使用同步...但我总是对 JLS 的那部分感到头痛,所以使用原子引用)

Rather than wonder, why not just use the atomic references in java.util.concurrent?

(and for what it's worth, my reading of happens-before does not guarantee that Thread2 will see changes to mutable unless it also uses synchronized ... but I always get a headache from that part of the JLS, so use the atomic references)

左秋 2024-10-12 03:42:26

如果您将可变的易失性设置为可变,则可以在“廉价阅读中详细了解-写锁

It will be fine if you make mutable volatile, details in the "cheap read-write lock"

游魂 2024-10-12 03:42:26

您绝对确定只有在调用 setter 后才会调用 getter 吗?如果是这样,则不需要同步 getter,因为并发读取不需要同步。

如果 get 和 set 有可能同时调用,那么您肯定需要同步两者。

Are you absolutely sure that the getter will be called only after the setter is called? If so, you don't need the getter to be synchronized, since concurrent reads do not need to synchronized.

If there is a chance that get and set can be called concurrently then you definitely need to synchronize the two.

天荒地未老 2024-10-12 03:42:26

如果您非常担心读取线程的性能,那么您要做的就是使用正确的同步或易失性或原子引用读取一次值。然后将该值分配给一个普通的旧变量。

对普通变量的赋值保证在原子读取之后发生(因为它怎么能获取该值?),并且如果该值永远不会再被另一个线程写入,那么就一切就绪了。

If you worry so much about the performance in the reading thread, then what you do is read the value once using proper synchronization or volatile or atomic references. Then you assign the value to a plain old variable.

The assign to the plain variable is guaranteed to happen after the atomic read (because how else could it get the value?) and if the value will never be written to by another thread again you are all set.

忆依然 2024-10-12 03:42:26

我认为你应该从正确的东西开始,然后当你知道有问题时进行优化。我只会使用 AtomicReference 除非几纳秒太长。 ;)

public static void main(String... args) {
    AtomicReference<String> ars = new AtomicReference<String>();
    ars.set("hello");
    long start = System.nanoTime();
    int runs = 1000* 1000 * 1000;
    int length = test(ars, runs);
    long time = System.nanoTime() - start;
    System.out.printf("get() costs " + 1000*time / runs + " ps.");
}

private static int test(AtomicReference<String> ars, int runs) {
    int len = 0;
    for (int i = 0; i < runs; i++)
        len = ars.get().length();
    return len;
}

打印

get() costs 1219 ps.

ps 为皮秒,其中 为百万分之一微秒。

I think you should start with something which is correct and optimise later when you know you have an issue. I would just use AtomicReference unless a few nano-seconds is too long. ;)

public static void main(String... args) {
    AtomicReference<String> ars = new AtomicReference<String>();
    ars.set("hello");
    long start = System.nanoTime();
    int runs = 1000* 1000 * 1000;
    int length = test(ars, runs);
    long time = System.nanoTime() - start;
    System.out.printf("get() costs " + 1000*time / runs + " ps.");
}

private static int test(AtomicReference<String> ars, int runs) {
    int len = 0;
    for (int i = 0; i < runs; i++)
        len = ars.get().length();
    return len;
}

Prints

get() costs 1219 ps.

ps is a pico-second, with is 1 millionth of a micro-second.

魔法唧唧 2024-10-12 03:42:26

这可能永远不会导致不正确的行为,但除非您还保证线程启动的顺序,否则您不一定能保证编译器不会在 Thread1 中的写入之前对 Thread2 中的读取进行重新排序。更具体地说,整个 Java 运行时只需保证线程像串行运行一样执行。因此,只要线程在优化下串行运行相同的输出,整个语言堆栈(编译器、硬件、语言运行时)就可以做到
几乎随心所欲。包括允许 Thread2 缓存 LockQuestion.getMutable() 的结果。

实际上,如果发生这种情况,我会感到非常惊讶。如果您想保证这种情况不会发生,请将 LockQuestion.mutable 声明为 final 并在构造函数中进行初始化。 或者使用以下习惯用法

private static class LazySomethingHolder {
  public static Something something = new Something();
}

public static Something getInstance() {
  return LazySomethingHolder.something;
}

This probably will never result in incorrect behavior, but unless you also guarantee the order that the threads startup in, you cannot necessarily guarantee that the compiler didn't reorder the read in Thread2 before the write in Thread1. More specifically, the entire Java runtime only has to guarantee that threads execute as if they were run in serial. So, as long as the thread has the same output running serially under optimizations, the entire language stack (compiler, hardware, language runtime) can do
pretty much whatever it wants. Including allowing Thread2 to cache the the result of LockQuestion.getMutable().

In practice, I would be very surprised if that ever happened. If you want to guarantee that this doesn't happen, have LockQuestion.mutable be declared as final and get initialized in the constructor. Or use the following idiom:

private static class LazySomethingHolder {
  public static Something something = new Something();
}

public static Something getInstance() {
  return LazySomethingHolder.something;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文