• 多线程《一》读写锁提高锁的效率


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


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

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

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

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

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

    场景《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

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

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

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

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

  • 相关阅读:
    oracle 递归查询 查询当前选中节点的所有子节点
    sql 常见操作
    【转】VS2008制作打包程序将安装路径写入注册表
    HTML字符集大全
    Oracle左连接,右连接
    Ubuntu下root用户和user用户如何进行相互切换
    【转】 vs2008 用文件部署生成的exe安装包
    C# 中out 和 ref 关键字的区别
    【转】vs2008安装部署程序时如何设置程序开机启动
    【转】vs2008安装部署工程制作教程
  • 原文地址:https://www.cnblogs.com/micheng123456/p/8359768.html
Copyright © 2020-2023  润新知