【问题标题】:accessing a String across multiple threads跨多个线程访问字符串
【发布时间】:2014-07-18 18:34:33
【问题描述】:

我在这里寻找一些输入。我有一个单例类,其中包含一个值,该值由该类中的方法每隔几秒更新一次。现在,跨多个线程访问这个值是通过同步完成的,我想消除它。这有意义吗?

class DataSegment {

    private MetricsUpdater metrics = new MetricsUpdater.getInstance();

    public String printValues() {
        StringBuilder sb = new StringBuilder();
        sb.append(value1);
        sb.append(morevalues);
        sb.append(metrics.myValue); // this is the value that's currently synchronized
        return sb.toString();
    }
}


class MetricsUpdater {

    private String myValueSynchronized;
    public String myValue;

    public static MetricsUpdater getInstance() {
        if (theInstance == null) {
            theInstance = new MetricsUpdater();
        }
        return theInstance;
    }

    // this runs on a timer but to keep it simple I'll just define the method...
    private void updateMetrics() {

        synchronized(myValue) {
            // also, to keep things simple, I've replaced all of the actual logic with a method called someMethodToUpdateMyValue()
            myValueSynchronized = someMethodToUpdateMyValue();
            myValue = myValueSynchronized;
        }
    }
}

可能有许多 DataSegment 实例都从 myValue 中读取,但指标类是单例的。 myValue 仅每 5 秒左右更新一次,并且仅允许 MetricsUpdater 对其进行写入。这有意义吗?

如果只允许所有其他线程读取它,它甚至需要同步吗?我已经为此运行了大量的 JUnit 测试,创建了许多 DataSegment 类的实例,所有的打印值都像疯了一样,我还没有看到任何并发问题。

【问题讨论】:

  • 一定会喜欢一个绝对没有反馈的反对票。
  • 用户不需要证明他们的行为是正当的。但是,我同意这不应该仅仅因为你正在尝试一些不起作用的东西而被否决。我会投票支持它。
  • 同意,但我正在寻找的只是反馈。没有信息的反对票不是任何有用的反馈。不过谢谢,我很感激。

标签: java multithreading synchronization


【解决方案1】:

您的代码存在一些问题。

第一个问题

synchronized(myValue) {
    myValueSynchronized = someMethodToUpdateMyValue();
    myValue = myValueSynchronized;
    Thread.sleep(100); 
}

您的关键部分错误,因为正在锁定 myValue。假设您在退出临界区之前放置了一个 Thread.sleep(100) 。那么这意味着其他线程将锁定新的 myValue 实例,从而可以进入临界区。如果它是一个时间线程并且它的频率非常高。然后你可以更新过时的更新来覆盖新的。 无论如何,在此类监视器上获取锁定是一种不好的做法。使用 ReentrantLock 或对 String 的一些最终引用进行同步。

第二个问题

    public static MetricsUpdater getInstance() {
        if (theInstance == null) {
            theInstance = new MetricsUpdater();
        }
        return theInstance;
    }

您的单例代码已损坏。使用 DCL(双重检查锁定见下文我的解决方案部分)。 或者使用私有静态 MetricsUpdater theInstance = new MetricsUpdate();。后者更好,

第三个问题

 sb.append(metrics.myValue); 

上述代码应在同步上下文中调用或声明为 volatile。后者更好

解决方案 1 - 假设 someMethodToUpdateMyValue 是线程安全的

class MetricsUpdater {

    private static volatile MetricsUpdater theInstance;
    public volatile String myValue;

    /**
     * DCL . Please avoid
     * Better use 
     * private static MetricsUpdater theInstance = new MetricsUpdate();
     */
    public static MetricsUpdater getInstance() {
        if (theInstance == null) {
            synchronized(MetricsUpdate.class) {
                 if(theInstance == null) {
                     theInstance = new MetricsUpdater();
                 }
            }
        }
        return theInstance;
    }

    // this runs on a timer but to keep it simple I'll just define the method...
    // if your someMethodToUpdateMyValue is thread safe
    private void updateMetrics() {
            myValue = someMethodToUpdateMyValue();
    }
}

解决方案 2:假设 someMethodToUpdateMyValue 不是线程安全的

不需要同步是参考读/写是原子的 我们已将 myValue 声明为 volatile

class MetricsUpdater {

 private static volatile MetricsUpdater theInstance;
 public volatile String myValue;

 /**
 ** Use ReentrantLock instead
 */
 private final  Object lock  = new Object();


     /**
     * DCL . Please avoid
     * Better use 
     * private static MetricsUpdater theInstance = new MetricsUpdate();
     */
    public static MetricsUpdater getInstance() {
        if (theInstance == null) {
            synchronized(MetricsUpdate.class) {
                 if(theInstance == null) {
                     theInstance = new MetricsUpdater();
                 }
            }
        }
        return theInstance;
    }

// this runs on a timer but to keep it simple I'll just define the method...
private void updateMetrics() {
    synchronized(lock) {
        myValue = someMethodToUpdateMyValue();
    }
}

}

【讨论】:

  • 关于问题 1:一个好的经验法则是,任何时候你写 synchronized (foo),那么 foo 应该是一个 final 字段。如果你不能使 foo 成为最终的,那么要么你的程序坏了,要么你做的事情太棘手而不能在生产代码中允许。
【解决方案2】:

它确实需要同步,或者被多个线程读取的变量需要标记为volatile(或其他任何导致java刷新变量值的东西)。 Java 内存模型不保证一个线程将(永远)看到另一个线程写入的变量的值。在实践中,值经常被多个线程正确看到,但如果要确保它,必须正确同步(或使用 volatile/locks/etc)以确保值被刷新。

【讨论】:

  • 这看起来是 volatile 的完美案例。
【解决方案3】:

是的,myValue 的读取必须发生在同一个锁上的同步块中才能看到 myValue 的最新值。

所以你可以在 myValue 后面加上:

synchronized (metrics)
{
     sb.append(metrics.myValue); // this is the value that's currently synchronized
}

并更改为:

synchronized(this) {
    // also, to keep things simple, I've replaced all of the actual logic with a method called someMethodToUpdateMyValue()
    myValueSynchronized = someMethodToUpdateMyValue();
    myValue = myValueSynchronized;
}

从我看到的 myValueSynchronized 也不需要。只要您根据需要使 myValue 的值与对象中的其余数据保持一致,就可以使用 myValue。

【讨论】:

  • 视情况而定。访问同步字段的成本可能相对较高(多少取决于 JVM、操作系统和主机架构)。如果有一个线程频繁访问该字段,而其他线程访问它的频率较低,则一个线程拥有自己的非易失性副本可能是有意义的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多