【问题标题】:Java fine-grained synchronization in getter/setter methods and singleton pattern of a class类的 getter/setter 方法和单例模式中的 Java 细粒度同步
【发布时间】:2012-08-20 19:49:29
【问题描述】:

我正在尝试使用synchronization java 指令在类中实现细粒度同步,即同步尽可能少的代码。我将内联注释代码,以解释我的工作和在代码之后我会问你如何改进代码:

public class MyClass {
    private static volatile MyClass singletonInstance = null;

    private HashMap<String, Integer> mHashMap = null;
    private String mStringA = null;
    private String mStringB = null;


     // Use double check technique to use synchronization only 
     // at the first getInstance() invocation
    public static MyClass getInstance() {
        if (singletonInstance == null) {
            synchronized (MyClass.class) {
                if (singletonInstance == null)
                    singletonInstance = new MyClass();
                    // Initialize class member variables
                    singletonInstance.mHashMap = new HashMap<String,Integer>();
                    singletonInstance.mStringA = new String();
                    singletonInstance.mStringB = new String();
            }

        }
        return singletonInstance;
    }

    // The following two methods manipulate the HashMap mHashMap
    // in a secure way since they lock the mHashMap instance which
    // is always the same and is unique
    public Integer getIntegerFromHashmap(String key) {
        synchronized (mHashMap) {
            return mHashMap.get(key);
        }
    }

    public void setIntegerIntoHashmap(String key, Integer value) {
        synchronized (mHashMap) {
            mHashMap.put(key, value);
        }
    }

    // With the two String members mStringA and mStringB the problem is 
    // that the instance of String pointed by the member is varied by the 
    // setter methods, so we can not lock in a fine grained way and we
    // must lock on the singletonInstance.
    public String getStringA() {
        synchronized (singletonInstance) {
            return mStringA;
        }
    }

    public String getStringB() {
        synchronized (singletonInstance) {
            return mStringB;
        }
    }

    public void setStringA(String newString) {
        synchronized (singletonInstance) {
            mStringA = newString;
        }
    }

    public void setStringB(String newString) {
        synchronized (singletonInstance) {
            mStringB = newString;
        }
    }
}

我不喜欢两个String 成员变量的getter 和setter 方法是锁定singletonInstance 可以使试图访问mStringB 的线程等到正在操作mStringA 的线程释放它的锁。在这种情况下你会怎么做?您是否会在MyClass 中创建两个成员变量,如private final Integer mStringALock = new Integer(0)private final Integer mStringBLock = new Integer(0),并分别在mStringAmStringB 的getter 和setter 方法的同步块中使用它们?

如果您对如何改进上述代码以及针对String 成员变量的细粒度同步提出的变体有一些想法,欢迎您:)

【问题讨论】:

    标签: java synchronization locking singleton


    【解决方案1】:

    通常更简单的解决方案更易于实施。我还会使用 2004 年添加的并发库。

    这不需要显式锁定,并且每个容器都是线程安全的。

    您可以使用 AtomicReference,但在这种情况下,它不会为您提供任何 volatile 尚未提供的东西。 (正如 kdgregory 指出的那样)您可能会在更复杂的情况下使用 AtomicReference。

    public enum MyClass {
        INSTANCE;
    
        private final Map<String, Integer> mHashMap = new ConcurrentHashMap<String, Integer>();
        private volatile String mStringA = null;
        private volatile String mStringB = null;
    
    
        // The following two methods manipulate the HashMap mHashMap
        // in a secure way
        public Integer getIntegerFromHashmap(String key) {
            return mHashMap.get(key);
        }
    
        public void setIntegerIntoHashmap(String key, Integer value) {
            mHashMap.put(key, value);
        }
    
        public String getStringA() {
            return mStringA;
        }
    
        public String getStringB() {
            return mStringB;
        }
    
        public void setStringA(String newString) {
            mStringA = newString;
        }
    
        public void setStringB(String newString) {
            mStringB = newString;
        }
    }
    

    【讨论】:

    • AtomicReferencevolatile 相比,不会给你买任何东西。
    【解决方案2】:

    是的,如果您希望线程能够同时调用这两个方法,则需要两个单独的锁。我会说没有什么可以改进的。

    但是,我注意到您的 getInstance() 方法试图最小化要同步的块的大小,但您实际上并没有做到这一点,即您也检查了同步块内的 singletonInstance == null。所以,我认为用synchronized 来限定整个方法会更好。

    它缩短了该方法的代码,也使它更自然一点。

    【讨论】:

    • 如果你同步方法,同步发生在每次getInstance 调用,而在我的代码中它只发生在singletonInstance 变量为空时。然后,可能会发生两个或多个线程发现它为空,因此我们不能立即分配一个新实例,我们必须在类上同步,再次检查空singletonInstance,然后分配一个新类。您以双重检查为代价来减少同步影响,这只会在必须首先实例化类时才会发生。
    【解决方案3】:

    从哪里开始...

    好的,双重检查锁定:它已经坏了(仍然),不要使用它。如果您觉得必须使用单例(实际上,它们通常是个坏主意,请改用依赖注入),然后同步 getter 方法,并快速返回。在这种情况下,有争议的同步的可能性非常低,除非您拥有真正大量的内核(如数千个)并且不断调用 getter 方法。

    HashMap 替换为ConcurrentHashMap。 Doug Lea 在并发编码方面比你我都好。

    将字符串变量标记为 volatile 并且不同步它们。

    【讨论】:

    • 当我再次查看您的代码时,我怀疑您的真正问题是尝试以原子方式执行多个操作。与其要求具体的指示,不如描述一下您要完成的工作,以及您认为需要同步所有内容的原因。
    • 对不起,但我不认为我的代码中实现的双重检查锁定被破坏了......你能提供更多细节和论据你所说的吗?我知道并发访问在我正在开发的平台(Android 手机)上不会经常发生,但我喜欢学习和理解实现我的想法的最佳技术:)
    • 由于你在单例引用上使用volatile,你建立了一个happens-before关系,并且在这种情况下双重检查锁起作用。但它仍然是一个微优化,不会给你带来太多好处。
    • @kdgregory 你说的没错,是微优化,但是因为容易实现和好记,我觉得是实现单例的好方法吧?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-04
    • 1970-01-01
    • 2011-12-29
    • 2012-06-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多