zoukankan      html  css  js  c++  java
  • 多线程《一》读写锁提高锁的效率

    读写锁的作用
    为什么要用读写锁
    我们都知道,读写锁可以提高效率,但是怎么提高效率呢?


    我们通常说到读写锁的时候就会说:读数据的时候用读锁,写数据的时候,用写锁,读锁是共享锁,也就是说,可以一起去读数据相互之间不影响,
    和没上锁,好像也没什么区别。

    写锁是排它锁,当一个线程进入到写锁之后,那么其它的线程,就都只能等待了。

    上面说到读取数据的时候用读锁,好像和没上锁,没什么区别?真的没区别吗?答案肯定是有区别。

    其实如果你弄多线程出来整个流程只是为了去读取数据,没有对你读的数据做写的操作,那还真是没必要去上什么锁,浪费代码。

    模拟一个简单不需要上锁的场景:

    场景《1》:现在有三个线程都是要去读取数据num,变量num的数据又不会变,你们三个线程想怎么读就怎么读,我才懒得管你,读出来的数据都是一样的,又没什么影响。(当然如果有需求,要按顺序读取的例外。)

    那什么时候才去用读锁呢?

    其实我们要用到读锁的时候,都是伴随着要用到写锁,也就是说读锁是和写锁打配合的。

    现在我们把上面的场景变一下:

    场景《2》:现在还是有三个线程,但是现在是有两个线程要去读数据num,而一个线程要去给num附上新值。

    这种情况下:如果我们不加锁,就会出现数据的脏读

    解决脏读的方法有很多种,比如说synchronized方法,synchronized代码块,ReentrantLock写锁,或者我们这里要说的的读写锁一起用等等。。。。。

    我们就拿synchronized来和读写锁比较一下:

    public class TestNum {
        public static void main(String[] args) {
            final Num num = new Num();
            Runnable worker = new Runnable() {
                @Override
                public void run() {
                    num.getnum();
                }
            };
            Runnable worker1 = new Runnable() {
                @Override
                public void run() {
                    num.getnum();
                }
            };
            Runnable worker2 = new Runnable() {
                @Override
                public void run() {
                    num.setnum();
                }
            };
            new Thread(worker,"第一个").start();
            new Thread(worker1,"第二个").start();
            new Thread(worker2,"第三个").start();
        }
        
    }
    
    class Num{
        private Integer num =0;
        //获取num
        public synchronized void getnum(){
            System.out.println(Thread.currentThread().getName()+"读num的时候进来:"+num);
        }
        //设置num
        public synchronized void setnum(){
            //设置num=1
            num=1;
            System.out.println(Thread.currentThread().getName()+"写后的num:"+num);
        }
    }

    运行会出现三种结果

    第一种得到结果:

    第一个读num的时候进来:0
    第三个写后的num:1
    第二个读num的时候进来:1

    第二种得到结果:

    第一个读num的时候进来:0
    第二个读num的时候进来:0
    第三个写后的num:1

    第三种得到结果

    第三个写后的num:1
    第一个读num的时候进来:1
    第二个读num的时候进来:1

     

    结果数据很正常,但是我们会发现第一次的结果数据,很像是串行的,也更好说明一下问题。我们知道synchronized方法会使三个线程,不管你是读线程还是写线程,每次只能进去一个,也就是说,一个线程进入到了读数据getnum()里面去了

    其它两个线程都是需要等这个线程完成操作释放锁,其它线程才能去读数据getnum()或者写数据setnum()。这样做就会不管你是读线程还是写线程都是串行的。串行的肯定效率低一点。

    如何能提高效率?

    我们可以想一下,如果两个读数据的线程,能并行去运行,一起去读数据getnum(),只让写数据的那个线程等待,这样我们的数据也不会发生脏读,而且效率也是会提高一点。

    要完成这个操作,就可以用到我们的读写锁

    读写锁代码如下:

    public class TestNum {
        public static void main(String[] args) {
            final Num num = new Num();
            Runnable worker = new Runnable() {
                @Override
                public void run() {
                    num.getnum();
                }
            };
            Runnable worker1 = new Runnable() {
                @Override
                public void run() {
                    num.getnum();
                }
            };
            Runnable worker2 = new Runnable() {
                @Override
                public void run() {
                    num.setnum();
                }
            };
            new Thread(worker,"第一个").start();
            new Thread(worker1,"第二个").start();
            new Thread(worker2,"第三个").start();
        }
        
    }
    
    class Num{
        private Integer num =0;
        ReentrantReadWriteLock readwrlock = new ReentrantReadWriteLock();
        //获取num
        public  void getnum(){
            //读数据的时候用读锁
            try {
                //加读锁
                readwrlock.readLock().lock();
                System.out.println(Thread.currentThread().getName()+"读num的时候进来:"+num);
            } catch (Exception e) {
                e.printStackTrace();
            }finally{
                //解锁
                readwrlock.readLock().unlock();
            }
        }
        //设置num
        public  void setnum(){
            //写数据的时候用写锁
            try {
                //加写锁
                readwrlock.writeLock().lock();
                //设置num=1
                num=1;
                System.out.println(Thread.currentThread().getName()+"写后的num:"+num);
            } catch (Exception e) {
                e.printStackTrace();
            }finally{
                //解锁
                readwrlock.writeLock().unlock();
            }
        }
    }

    没有特殊情况的话《就是一个读锁,读完了,另一个读锁居然还没进来,被写锁拿到写锁了,这种情况,上面的代码应该是不会发生的》,运行结果一般,只会有两种:

    第一种:

    第一个读num的时候进来:0
    第二个读num的时候进来:0
    第三个写后的num:1

    第二种

    第三个写后的num:1
    第二个读num的时候进来:1
    第一个读num的时候进来:1

    看着这两种结果,我们会发现,两个读操作一直都是在一起的。

    这就是我们读写锁机制的妙用:

    当一个线程得到读锁的时候,不允许其它线程得到写锁,但是可以让其它线程得到读锁,所以读操作之间就可以并行运行,相互之间不需要等待。

    当一个线程得到写锁的时候,就会不允许其它线程得到写锁或者读锁,但是这个线程可以让自己在有写锁的情况下,获取到读锁,这个我们下篇再来讲这个机制,读写锁的锁降级的机制

  • 相关阅读:
    68
    56
    Django manager 命令笔记
    Django 执行 manage 命令方式
    Django 连接 Mysql (8.0.16) 失败
    Python django 安装 mysqlclient 失败
    H.264 SODB RBSP EBSP的区别
    FFmpeg—— Bitstream Filters 作用
    MySQL 远程连接问题 (Windows Server)
    MySQL 笔记
  • 原文地址:https://www.cnblogs.com/micheng123456/p/8359768.html
Copyright © 2011-2022 走看看