Java并发性volatile,用于读取同步以进行写入

时间:2018-07-09 15:11:29

标签: java concurrency

我需要创建一个具有线程间共享对象的类(让我们称之为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是线程安全的,我的解决方案就可以正常运行,但是由于对可变字段的多次访问,它可能会降低性能。我将使用升级双重检查锁定解决方案来最大程度地改善它。我还将深入分析这里提到的所有其他可能性。

3 个答案:

答案 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();
        }
    }

}