我需要创建一个具有线程间共享对象的类(让我们称之为SharedObject)。关于SharedObject的特殊之处在于,它拥有一个String,它将在多线程环境中返回,有时通过更改对新创建对象的字段引用,可以写入整个SharedObject。
我不想在同一台监视器上同时进行读写操作,因为写操作很少发生,而读操作却很常见。因此,我做了以下事情:
public class ObjectHolder {
private volatile SharedObject sharedObject;
public String getSharedObjectString() {
if (!isObjectStillValid()) {
obtainNewSharedObject()
}
return sharedObject.getCommonString()
}
public synchronized void obtainNewSharedObject() {
/* This is in case multiple threads wait on this lock,
after first one obtains new object the others can just
use it and should not obtain a new one */
if(!isObjectStillValid()) {
sharedObject = new SharedObject(/*some parameters from somewhere*/)
}
}
}
根据我从文档中以及stackoverflow上所读的内容,synchronized
关键字将确保只有一个线程可以访问同一对象实例上的同步块(因此不会发生写竞争/多次不必要的写操作)而字段引用上的volatile
关键字将确保将引用值直接写入主程序存储器(不在本地缓存)。
我还有其他陷阱吗?
我想确保在写入sharedObject
的同步块中,最晚在释放sharedObject
的锁定时,任何其他线程都会出现obtainNewSharedObject()
的新值。如果不能保证,我可能会遇到不必要的写入和替换正确的值的情况,这是这种情况下的大问题。
我知道绝对安全,我可以自己制作getSharedObjectString()
synchronized
,但是如前所述,我不想在不需要时阻止阅读。
这种读取方式是非阻塞的,当发生写入情况时,它就是阻塞的。
我可能应该提到方法isObjectStillValid()
是线程独立的(完全基于SharedObject和系统时钟),因此有效的无线程检查可用于写方案。
编辑:请注意,我在stackoverflow上找不到类似的主题,但它可能存在。抱歉,是这种情况。
Edit2:谢谢您的所有评论。进行编辑,因为显然我还不能投票(可以,但不显示)。只要isObjectStillValid
是线程安全的,我的解决方案就可以正常运行,但是由于对可变字段的多次访问,它可能会降低性能。我将使用升级双重检查锁定解决方案来最大程度地改善它。我还将深入分析这里提到的所有其他可能性。
答案 0 :(得分:1)
您为什么不使用AtomicReference。它使用乐观锁定,这意味着不涉及实际的线程锁定。内部使用Compare and Swap。如果您看一下实现,它会在实现中使用volatile,我相信Doug Lea可以正确实现它:)
除此之外,许多读者和某些作家之间还有许多同步方法-ReadWriteLock
答案 1 :(得分:0)
这看起来像经典的double-checked locking模式。尽管您的实现在逻辑上是正确的-由于在volatile
上使用了sharedObject
-它可能不是最有效的。
在链接的Wikipedia页面上显示了Java 1.5的推荐模式。
// Works with acquire/release semantics for volatile in Java 1.5 and later
// Broken under Java 1.4 and earlier semantics for volatile
class Foo {
private volatile Helper helper;
public Helper getHelper() {
Helper localRef = helper;
if (localRef == null) {
synchronized(this) {
localRef = helper;
if (localRef == null) {
helper = localRef = new Helper();
}
}
}
return localRef;
}
// other functions and members...
}
请注意使用localRef
来访问helper
字段。在简单情况下,这将对volatile字段的访问限制为一次读取,而不是两次。一次用于检查,一次用于退货。在推荐的模式示例之后,再次访问Wikipedia页面。
注意局部变量“ localRef”,这似乎是不必要的。这样的效果是,在辅助程序已经初始化的情况下(即,大部分时间),仅对易失字段进行一次访问(由于“ return localRef;”而不是“ return helper;”),因此可以改善方法的整体性能高达25%。[7]
根据isObjectStillValid()
访问sharedObject
的方式,您可能会从类似的模式中受益。
答案 2 :(得分:0)
这听起来像是正确使用ReadWriteLock。
基本思想是可以同时有多个读者或一个作家。您可以在这里找到Example在List实现中的使用方法。
如果粘贴面朝下,请复制粘贴:
import java.util.*;
import java.util.concurrent.locks.*;
/**
* ReadWriteList.java
* This class demonstrates how to use ReadWriteLock to add concurrency
* features to a non-threadsafe collection
* @author www.codejava.net
*/
public class ReadWriteList<E> {
private List<E> list = new ArrayList<>();
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
public ReadWriteList(E... initialElements) {
list.addAll(Arrays.asList(initialElements));
}
public void add(E element) {
Lock writeLock = rwLock.writeLock();
writeLock.lock();
try {
list.add(element);
} finally {
writeLock.unlock();
}
}
public E get(int index) {
Lock readLock = rwLock.readLock();
readLock.lock();
try {
return list.get(index);
} finally {
readLock.unlock();
}
}
public int size() {
Lock readLock = rwLock.readLock();
readLock.lock();
try {
return list.size();
} finally {
readLock.unlock();
}
}
}