跨多个线程访问字符串

AWT*_*AWT 5 java multithreading synchronization

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

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

ver*_*tas 5

您的代码存在一些问题。

第一个问题

synchronized(myValue) {
    myValueSynchronized = someMethodToUpdateMyValue();
    myValue = myValueSynchronized;
    Thread.sleep(100); 
}
Run Code Online (Sandbox Code Playgroud)

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

第二个问题

    public static MetricsUpdater getInstance() {
        if (theInstance == null) {
            theInstance = new MetricsUpdater();
        }
        return theInstance;
    }
Run Code Online (Sandbox Code Playgroud)

您的单例代码已损坏。使用 DCL(双重检查锁定,请参阅下面我的解决方案秒)。或者使用 private static MetricsUpdater theInstance = new MetricsUpdate();。后者比较好,

第三个问题

 sb.append(metrics.myValue); 
Run Code Online (Sandbox Code Playgroud)

上面的代码应该在同步上下文中调用或声明为易失性。后者更好

解决方案 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

不需要同步,引用读/写是原子的,并且我们已将 myValue 声明为易失性

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

}