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

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


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

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

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

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

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

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

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

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

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

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

原文地址:https://www.cnblogs.com/micheng123456/p/8359768.html