Java并发之读写锁

读写锁

首先了解读写锁之前,可以先来聊聊其它锁。

比如说乐观锁和悲观锁。

什么为乐观锁,什么为悲观锁呢?这个名字听起来就很有趣有没有!
请添加图片描述
如图所示:

其实有点像之前做UE4项目时用到的P4V,就是用来远程在线协同合作中小型项目。

里面就讲到了,在把项目拿出来修改的时候,必须上锁,防止别人的误操作。

  • 悲观锁就是只允许一个人进行修改,在修改的时候对资源进行上锁,其它人操作不了。
  • 乐观锁就是可以同时允许两个人进行操作,但是每个操作的时候都有版本号。
    比如A和B同时进行操作,而A先操作完提交了,版本号从原本1.0改为了1.1,所以当B要提交的时候,发现版本号已经为1.1了,就没办法提交。
    这也解释了为什么当时p4v保存的是先提交的人的一份。

好!讲完乐观悲观锁,我们来讲讲主角读写锁

读锁也被称为共享锁
写锁被称为独占锁

然后读写锁都会发生死锁。

首先来看一个正常案例:

public class Demo2 {
    //资源类
    static class MyCache {
        //创建 map 集合
        private volatile Map<String,Object> map = new HashMap<>();
        //创建读写锁对象
       // private ReadWriteLock rwLock = new ReentrantReadWriteLock();
        //放数据
        public  void  put(String key,Object value) {
            //添加写锁
           // rwLock.writeLock().lock();
            try {
                System.out.println("线程"+Thread.currentThread().getName()+" 正在写入操作"+key);
                        //暂停一会
                        TimeUnit.MICROSECONDS.sleep(300);
                //放数据
                map.put(key,value);
                System.out.println("线程"+Thread.currentThread().getName()+"写完了 "+key);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //释放写锁
               // rwLock.writeLock().unlock();
            }
        }
        //取数据
        public Object get(String key) {
            //添加读锁
           // rwLock.readLock().lock();
            Object result = null;
            try {
                System.out.println("线程"+Thread.currentThread().getName()+"正在读取操作 "+key);
                        //暂停一会
                        TimeUnit.MICROSECONDS.sleep(300);
                result = map.get(key);
                System.out.println("线程"+Thread.currentThread().getName()+"读完了 "+key);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //释放读锁
              //  rwLock.readLock().unlock();
            }
            return result;
        } }
        
    public static void main(String[] args) {
        MyCache myCache=new MyCache();
        for(int i=1;i<=5;i++)
        {
            final int num=i;
           new Thread(()-> {
               myCache.put(num + "", num + "");
           },String.valueOf(i)).start();}

        for(int i=1;i<=5;i++)
        {
            final int num=i;
            new Thread(()-> {
                myCache.get(num+" ");
            },String.valueOf(i)).start();}
        
    }
    }

怎么说呢?
就是多个线程同时操作一个资源类cache里面的map集合,可以对map写入操作,也可以读取操作。

按程序后面的顺序是先写再读。

添加延时只是为了让效果更加明显,因为在创建线程去写入之后,就直接延时,这样就可以很明显看出有没有 读的进程会进去。(如果不加延时,仅靠线程的随机触发,偶然性很高)

而结果是会的。

在这里插入图片描述
这就是我们运行的结果,就是读操作等不及写操作完成的3秒,就已经开始执行了。

但是!但是这不是我们想要的结果,我们要的结果是得线程写入完成之后,才进行读的线程。

  • 于是我们的第一个想法是两个方法同时加了synchronized。那让我们试一下,看一下运行效果如何?

在这里插入图片描述
这就是运行效果,可以说一部分目的达到了。
也就是说,确实不会在写的同时去进行读了。
(因为两个方法都加了锁,也就是每个执行了该方法的线程都对资源类上了锁,同时只能运行自己该线程执行,即使睡眠了,别人也抢不到,得等我醒来,这就是锁的作用)

虽然达到了一定的效果,但是还不是我们真正想要的结果。我们真正想要的是,可以同时读,但不能同时写。(而目前是不能同时读同时写)

  • 于是乎我们想到了,那么如果对写的方法上锁,而对读的方法不上锁,那么不就可以同时读而不同时写了吗?

多说无益,我们直接看运行结果:

在这里插入图片描述
为什么会出现这种情况呢?我一时半会想不过来,并感觉知识体系遭到重创!

于是乎,我想了一下,8锁现象中好像没有一个方法加锁,一个不加的情况。

于是乎我就研究了一下。如果只有读的方法,读方法不加锁,那么运行情况是这样的:

在这里插入图片描述
如果只有写方法,写方法加锁,运行结果是这样的:

在这里插入图片描述
结合上面的情况,我做出了这样的假设(也不知道对不对)。

就是说一个方法加锁,一个方法不加锁,那么运行结果
在这里插入图片描述
它能确报写的操作不被其它写的操作插队,但是不能保证写的操作被另一个写的操作插队。

也就是说它只能保证锁住的方法,保证线程1写完线程2才会进来写,但是其中会有读的线程进来它就管不了了。

或许是这样吧,锁只能管自己的方法。

  • 但是这种效果还不是我们要达到的效果,我们要的效果是,能同时读,但不能同时写,且写的同时读不能进来捣乱。
  • 而要达成这种效果就只能用读写锁了。
   private ReadWriteLock rwLock = new ReentrantReadWriteLock();
   

创建一个ReadWriteLock现象,然后在写的方法添加写锁

  rwLock.writeLock().lock();

在读的地方添加读锁

  rwLock.readLock().lock();

最终就能达到想要的结果:

在这里插入图片描述
读写锁:
可以共享,提升性能。同时多人进行读操作。

但是也不是没有缺点:

  • 比如说锁饥饿,一直读,没有写操作。
最后讲一讲锁降级的事:

• 在线程持有读锁的情况下,该线程不能取得写锁(因为获取写锁的时候,如果发
现当前的读锁被占用,就马上获取失败,不管读锁是不是被当前线程持有)。
• 在线程持有写锁的情况下,该线程可以继续获取读锁(获取读锁时如果发现写
锁被占用,只有写锁没有被当前线程占用的情况才会获取失败)。

原因: 当线程获取读锁的时候,可能有其他线程同时也在持有读锁,因此不能把
获取读锁的线程“升级”为写锁;而对于获得写锁的线程,它一定独占了读写
锁,因此可以继续让它获取读锁,当它同时获取了写锁和读锁后,还可以先释
放写锁继续持有读锁,这样一个写锁就“降级”为了读锁。

确实,因为写锁可能同时很多线程在占用,同时读;而写锁只能一线程占用,必须写完!

所以锁降级是这样的:
在这里插入图片描述
在这里插入图片描述
这样是可以正常执行的,先获取写锁,再获取读锁,然后释放写锁,再释放读锁。

(同样可以执行读的操作)

但是如果反过来
在这里插入图片描述
在这里插入图片描述
这样只能读,但是不能写!

  • 读锁不能升级为写锁!
  • 写锁可以升级为读锁!

以上内容参考自b站视频https://www.bilibili.com/video/BV1Kw411Z7dF?p=32&spm_id_from=pageDriver;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值