多线程之读写锁ReadWriteLock的深度理解及用读写锁实现缓存《十》

版权声明:本文为HCG原创文章,未经博主允许不得转载。请联系[email protected] https://blog.csdn.net/qq_39455116/article/details/86649926

读写锁

1. 为什么要引入读写锁?
1. 重入锁是一个时刻只能一个线程访问,读写锁同一个时刻允许多个线程访问
2. 但是在写线程访问时,所有的读线程和其他写线程均被阻塞
3. 读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,
   通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升  
2. 读写锁特有的方法
1. readLock() 获取读锁
2. writeLock() 获取写锁
3. int getReadLockCount() 返回当前读锁被读的次数,而不等于获取读锁的线程数
   比如一个线程读了N次,返回的就是N
4. int getReadHoldCount() 返回当前线程获取读锁的次数
5. boolean is WriteLocked 返回当前写锁是否被获取
6. int getWriteHoldCount   返回当前写锁被读取的次数
3. 写锁
1. 写锁是一个支持重进入的排他锁,和重入锁一样,只有状态为0才释放当先线程写锁
2. 但是除了重入条件,写锁增加了一个读锁是否存在的判断?
如果存在读锁,则写锁不能被获取,原因是:
2.1 读写锁要确保写锁的操作要对读锁 可见,如果允许读锁在已经被获取的情况下对写锁获取,那么
正在允许的其它读线程就不能感知到当前写线程的操作
2.2 因此只有等待其它读线程都释放了锁,写操作才能被获取
2.3 而且写锁一旦被获取,其它读线程的后续均被阻塞
4. 读锁
1. 读锁是一个支持重进入的共享锁,能被多个线程同时获取
2. 如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入等待状态
3. 如果当前线程获取了写锁或者写锁未被获取,则当前线程(线程安全,依靠CAS保证)增加读状态,成功获取读锁。
4. 读锁每次释放均减少读状态,减少的值是1<<16
5. 锁降级
锁降级是指:写锁降级成为读锁
1. 一个拥有写锁的线程,释放写锁,再获取读锁,这种分段完成的过程不能称之为锁降级
2. 锁降级是指持住当前线程拥有的写锁,再获取读锁,随后释放先前拥有写锁的过程
// 锁降级的示例:

public void processData() {
        readLock.lock();
        if (!update) {
			// 必须先释放读锁
            readLock.unlock();
			// 锁降级从写锁获取到开始
            writeLock.lock();
            try {
                if (!update) {
					// 准备数据的流程(略)
                    update = true;
                }
                readLock.lock();
            } finally {
                writeLock.unlock();
            }
		// 锁降级完成,写锁降级为读锁
        }
        try {
			// 使用数据的流程(略)
        } finally {
            readLock.unlock();
        }
    }
6.为什么需要锁降级?

目的:保证数据的可见性

试想:
1. 如果当写前线程A不获取读锁,而是直接释放写锁
2. 另一个线程B获取写锁,修改数据,那么线程A是无法感知数据被修改了的
7. 读写锁支持锁升级吗?
不支持,为什么?
目的: 保证数据可见性
试想: 
     如果读锁已经被多个数据获取了,其中任意线程成功获取了写锁,并修改了数据
     则其更新对其它获取到的读锁都是不可见的
8. 用锁降级实现一个缓存
package duoxiancheng.reentrantLock.read;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class CacheMap {

    //声明一个map,用来作为缓存模型
    private static Map<String, Object> map = new HashMap<String, Object>();
    //声明一个读写锁
    private static ReadWriteLock rwl = new ReentrantReadWriteLock();


   //用synchronized关键字实现一个缓存
    public Object synGetValue(String key) {
        Object value = map.get(key);
        if (value == null) {
            synchronized (map) {
                value = map.get(key);
                if (value == null) {
                    value = "abc";//这里是去数据库查询
                    map.put(key, value);//将数据放到缓存模型中
                    System.out.println(Thread.currentThread().getName() + "syn方式实现,put也只执行一次");
                }
            }
        }
        return value;
    }
    
    
   //用读写锁的锁降级实现一个缓存
    public Object getValue(String key) {
        Object value = null;
        try {
            rwl.readLock().lock();//开启读锁
            value = map.get(key);
            if (value == null) {
                try {
                    rwl.readLock().unlock();//关闭读锁
                    rwl.writeLock().lock();//开启写锁
                    //这个地方需要再检查一下,不检查的话,这个缓存就实现不了
                    value = map.get(key);
                    if (value == null) {
                        value = "abc";//这里是去数据库查询
                        map.put(key, value);//将数据放到缓存模型中
                        System.out.println(Thread.currentThread().getName() + "=======这个只执行一次=========");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    rwl.writeLock().unlock();//关闭写锁
                    rwl.readLock().lock();//开启读锁
                    //System.out.println(Thread.currentThread().getName() + "----2----");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwl.readLock().unlock();//关闭读锁
            //System.out.println(Thread.currentThread().getName() + "----3----");
        }
        return value;

    }


    public static void main(String[] args) {
        CacheMap cacheMap = new CacheMap();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + cacheMap.getValue("readWrite"));

            }, "--------readWrite线程" + i).start();

            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + cacheMap.synGetValue("syn"));

            }, "synchronized线程" + i).start();
        }
    }
}

9. 结果分析
--------readWrite线程0=======这个只执行一次=========
--------readWrite线程0abc
synchronized线程0syn方式实现,put也只执行一次
synchronized线程0abc
synchronized线程1abc
--------readWrite线程1abc
synchronized线程2abc
--------readWrite线程2abc
--------readWrite线程3abc
synchronized线程3abc
synchronized线程4abc
--------readWrite线程5abc
--------readWrite线程4abc
--------readWrite线程6abc
synchronized线程6abc
--------readWrite线程7abc
synchronized线程5abc
--------readWrite线程8abc
synchronized线程7abc
--------readWrite线程9abc
synchronized线程8abc
synchronized线程9abc
发现满足了线程安全的定义,只有一个线程往其中put了数据,实现了缓存
但是其实如果只是缓存的存的话,用synchronized会更简单一些,效率两个都差不多

猜你喜欢

转载自blog.csdn.net/qq_39455116/article/details/86649926