您的位置:首页 > 其它

关于正确使用读写锁的思考

2015-09-23 21:19 411 查看
使用读写锁可以大幅度提升性能,在读远大于写线程数目时,

本文主要是关于读写锁的一些疑惑 ,主要是实现缓存时,下面这段代码演示了常见的实现缓存的代码:(你可以在很多其他的博客中看到:)

我这里参考了:http://blog.csdn.net/yangfanend/article/details/7381530 这篇博客中的实现 如下:

public class ReadWriteLockCache
{

    /**
     * @param args
     */
    public static void main(String[] args)
    {
        // TODO Auto-generated method stub

    }

    private Map<String, Object> cache = new HashMap<String, Object>();
    private ReadWriteLock rwl = new ReentrantReadWriteLock();

    public Object getData(String key)
    {
        rwl.readLock().lock();
        Object value = null;
        try
        {
            value = cache.get(key);
            if (value == null)
            {
                rwl.readLock().unlock();//line1
                rwl.writeLock().lock();//line2
                try
                {
                    if (value == null)//line3
                    {
                        value = "aaaa";// 实际失去queryDB();
                        //line5 -- 这里是否还应将查到的数据放入缓存 不然无法实现cache效果.
                        cache.put(key, value);
                    }
                }
                finally
                {
                    rwl.writeLock().unlock();
                }
                rwl.readLock().lock();
            }
        }
        finally
        {
            rwl.readLock().unlock();
        }
        return value;
    }

}


这里主要有2个疑问:

[1]line3 位置的判断是否是必须的?

[2]line3 位置的判断是否合理?

ps: 在line5的注释. //line5 – 这里是否还应将查到的数据放入缓存 不然无法实现cache效果.

下面说下我的见解,也希望有大牛可以一起指导下^-^

问题1:line3的位置是否是必须的?

答:是需要重新检查的. 考虑有线程1 执行到line1 后,然后被线程2抢先执行(切换到线程2去执行),那么很有可能这个key是已经被线程2put到缓存中去了. 所以是必须要重新检查的.

问题2:line3的位置的判断是否合理?

(1)这里我认为是不合理的.理由如下,Object value = null; 当获取未空的时候,这里获取的value对象是一个线程栈内的局部变量(意即它不会被别的线程更改,对它的访问是不存在多线程问题的,所以当它是空后,是不会变为非空的),那么我们在line3的检查是不合理的.

(2)如果需要检查,那么我们需要在获取写锁的情况下,重新调用map.get(key)来判断对象是否存在.(line4)

如下:

if (value == null)
            {
                rwl.readLock().unlock();//line1
                rwl.writeLock().lock();//line2
                try
                {
                    value = cache.get(key);//line4 
                    if (value == null)//line3
                    {
                        value = "aaaa";// 实际失去queryDB();
                        //then put in cache map
                        cache.put(key, value);
                    }
                }
                finally
                {
                    rwl.writeLock().unlock();
                }
                rwl.readLock().lock();
            }


我看到了jdk api doc中的一个例子:ReentrantReadWriteLock

package concurrent;

import java.util.concurrent.locks.ReentrantReadWriteLock;

class CachedData
{
    Object data;
    volatile boolean cacheValid;
    final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    void processCachedData()
    {
        rwl.readLock().lock();
        if (!cacheValid)
        {
            // Must release read lock before acquiring write lock
            rwl.readLock().unlock();//line1
            rwl.writeLock().lock();//line2
            try
            {
                // Recheck state because another thread might have
                // acquired write lock and changed state before we did.
                if (!cacheValid)//line3
                {
                    data = "aaa";
                    cacheValid = true;
                }
                // Downgrade by acquiring read lock before releasing write lock
                rwl.readLock().lock();
            }
            finally
            {
                rwl.writeLock().unlock(); // Unlock write, still hold read
            }
        }

        try
        {
            use(data);
        }
        finally
        {
            rwl.readLock().unlock();
        }
    }
}


是不是很像?

但是代码的关键区别(line3)就是一个是用的:

if (value == null)//line3

if (!cacheValid)//line3


为啥jdk的实现没有问题呢?

因为cacheValid是对象的一个状态变量 所以重新检查是有必要的.

而value不是(当获取不到时),它只是线程栈中的一个对象.不同的线程 该对象是不会一样的.

不知道这样的理解是否正确?

欢迎一起讨论,你可以发送邮箱到gaoxingliang@outlook.com 看到会立即回复.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: