我在这里寻找一些意见。我有一个单例类,其中包含一个值,该值每隔几秒由该类中的方法更新。现在,跨多个线程访问此值是通过同步完成的,我想消除。这有意义吗?
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中读取的,但metrics类是一个单例。 myValue仅每5秒更新一次,并且只允许MetricsUpdater写入。这有意义吗?
如果只允许所有其他线程读取它,它是否甚至需要同步?我已经对此运行了大量的JUnit测试,创建了DataSegment类的许多实例,所有打印值都像疯了一样,我还没有看到任何并发问题。
答案 0 :(得分:4)
您的代码存在一些问题。
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;
}
您的Singleton代码已损坏。使用DCL(双重检查锁定在我的解决方案中见下文)。 或者使用私有静态MetricsUpdater theInstance = new MetricsUpdate();.后者更好,
sb.append(metrics.myValue);
上述代码应在同步上下文中调用或声明为volatile。后者更好
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();
}
}
不需要同步,引用读/写是原子的 我们将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();
}
}
}
答案 1 :(得分:3)
它确实需要同步,或者多个线程读取的变量需要标记为volatile
(或任何导致java刷新变量值的其他内容)。 java内存模型不保证一个线程(永远)会看到另一个线程写入的变量的值。实际上,这些值通常可以被多个线程正确看到,但如果要确保它,则必须正确同步(或使用volatile / locks / etc)以确保刷新值。
答案 2 :(得分: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。