多线程先判断再操作免锁设计方案

近日,业务上要求要对一块缓存进行高效率的读写。一开始采用了读写锁的设计,后来发现单个线程单次需要进行成千上万次的读,导致了过多的加解锁的开销,效率实在不敢恭维。加锁的主要原因是多线程先判断再操作导致判定失效问题,最近一直在考虑如何进行免锁设计,结合之前看过的final内存语义,设计了一个没有锁的实现方式,然后用了一万个线程进行并发测试,暂时通过了测试,下面说说我的思路,如果有问题,大家可以狂喷一下。

首先读写锁的方式,大家应该是没问题的。我先阐述一下问题。

由上图可以看出,线程1判定的状态在读取缓存时已经失效了,导致读取的时候得到空指针异常。

下面是我的demo代码

public class NoLockCache{
    private class Lock {
        private final boolean isValid;
        private final HashMap<Integer, Integer> hashMap;
        public Lock(boolean isValid, HashMap<Integer, Integer> hashMap) {
            this.isValid = isValid;
            this.hashMap = hashMap;
        }
        public Integer get() {
            if (isValid) {
                LockSupport.parkNanos(10000000);
                return hashMap.get(1);
            } else {
                return 0;
            }
        }
    }
 
    private volatile Lock lock;
 
    public NoLockCache() {
        HashMap<Integer, Integer> hashMap = new HashMap<>();
        hashMap.put(1, 1);
        lock = new Lock(true, hashMap);
    }
 
    public Integer get() {
        return lock.get();
    }
 
    public void release() {
        lock = new Lock(false, new HashMap<>());
        HashMap<Integer, Integer> hashMap = new HashMap<>();
        hashMap.put(1, 1);
        lock = new Lock(true, hashMap);
    }
}

  

免锁设计
 

上面NoLockCache的get操作,委托给内部类Lock。

Lock的两个成员变量都是final类型的,这两个final类型的变量可以保证在初始化Lock时,isValid和缓存hashMap处在一个一致的状态(可以参考并发编程艺术第三章的final语义或者博客http://www.cnblogs.com/CLFR/p/6262433.html),别的线程看到Lock只有两个状态,isValid是false且缓存是空,或者isValid是true且缓存被填充状态。有人会说了,那Lock的get方法不也是先判断,再操作吗?其实这时候的Lock已经加载在栈中,这时候的Lock方法get结束前,Lock会维持一个快照的状态,不会抛出NPE。

posted @ 2017-03-02 17:11  编程小白菜  阅读(509)  评论(0编辑  收藏  举报