读写锁的使用以及降级优化

一.读写锁特征


读写锁的形式如下

    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    Lock readLock = readWriteLock.readLock();
    Lock writeLock = readWriteLock.writeLock();

1.读读共享


    volatile Integer a = 0;



    public void test1() {
        readLock.lock();
        try {
            System.out.println(a);
            TimeUnit.SECONDS.sleep(100); //阻塞不释放锁
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            readLock.unlock();
        }
    }

    public void test2(){
        readLock.lock();
        try {
            System.out.println(a);
            TimeUnit.SECONDS.sleep(100); //阻塞不释放锁
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            readLock.unlock();
        }
    }


    public static void main(String[] args) {
        ReadWriteLockTest readWriteLockTest = new ReadWriteLockTest();
        new Thread(() -> {
            readWriteLockTest.test1();
        }).start();
        new Thread(() -> {
            readWriteLockTest.test2();
        }).start();
    }

test1方法和test2方法都不释放锁的情况下,如果互斥那么a值只会打印一次
结果:
0
0
证明获取读锁的线程,可以同时执行不互斥

2.读写互斥

  volatile Integer a = 0;



    public void test1() {
        readLock.lock();
        try {
            System.out.println(a);
            TimeUnit.SECONDS.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            readLock.unlock();
        }
    }

    public void test2(){
        writeLock.lock();
        try {
            System.out.println(a);
        } finally {
            writeLock.unlock();
        }
    }


    public static void main(String[] args) throws InterruptedException {
        ReadWriteLockTest readWriteLockTest = new ReadWriteLockTest();
        new Thread(() -> {
            readWriteLockTest.test1();
        }).start();
        //让test1方法先执行
        TimeUnit.SECONDS.sleep(2);

        new Thread(() -> {
            readWriteLockTest.test2();
        }).start();
    }

test1方法先执行 也就是获取读锁线程先执行,看获取写锁的是否能执行成功。
结果:
0
只会打印一次说明读写互斥

3.写写互斥

 volatile Integer a = 0;



    public void test1() {
        writeLock.lock();
        try {
            System.out.println(a);
            TimeUnit.SECONDS.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            writeLock.unlock();
        }
    }

    public void test2(){
        writeLock.lock();
        try {
            System.out.println(a);
        } finally {
            writeLock.unlock();
        }
    }


    public static void main(String[] args) throws InterruptedException {
        ReadWriteLockTest readWriteLockTest = new ReadWriteLockTest();
        new Thread(() -> {
            readWriteLockTest.test1();
        }).start();
        //让test1方法先执行
        TimeUnit.SECONDS.sleep(2);

        new Thread(() -> {
            readWriteLockTest.test2();
        }).start();
    }

代码与读写互斥差不多把test1换成写锁
结果:
0
只会打印一次说明写写互斥

二、使用场景

  1. 读写互斥:读的时候 其他线程不能写。如果读取的过程不涉及对共享状态的修改推荐用readLock,即保证读取的过程中,状态量在其他线程中不会部分更新,导致读线程读取数据异常,同时也保证了读线程之间不会出现竞争消耗。
  2. 读读共享:减少锁竞争的优化手段
  3. 写写互斥:对状态量修改的过程中,其他线程不可以写,也不可以读
    比如:对一个链表添加一个元素,然后更新size。这个操作执行时,如果刚添加一个元素,还没有更新size值就被其他线程读到了,那么就出现size和链表长度不匹配的问题。如果添加过程中,其他线程也在添加修改,那么size++这种操作就会出现问题。

总结:在读多写少环境下,用readlock效率更高

三、锁降级

  ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    Lock readLock = readWriteLock.readLock();
    Lock writeLock = readWriteLock.writeLock();

    volatile Integer c;

    CountDownLatch countDownLatch = new CountDownLatch(1);

    public void test1() {
        try {
            writeLock.lock();  //比如接下来的操作需要初始化某个值,其他线程使用这个值,先上写锁保证其他线程等待其初始化不会获取空值,避免触发空指针异常
            countDownLatch.countDown(); //其他使用c的线程继续运行
            c = 100;
            
            //初始化完毕,但是后面还要用到c,所以不能让其他线程更改这个值,但是可以开放读权限让其他线程读
            readLock.lock();  // 1、先上读锁 (不能先释放写锁因为释放的一瞬间其他线程可能会获得锁,先上读锁在释放写锁更安全)
        } finally {
            writeLock.unlock(); //2、释放写锁
        }
        //1和2两个步骤就是锁降级的过程
        try {
            System.out.println("c现在初始化为" + c + "了"); //这里还要继续用到c,此时其他线程也可以读了,但是不能写
        } finally {
            readLock.unlock();
        }


    }


    public void test2() {
        try {
            countDownLatch.await();
            for (; ; ) {
                TimeUnit.MILLISECONDS.sleep(1000);
                readLock.lock();
                System.out.println(c.toString());
                readLock.unlock();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) throws InterruptedException {
        ReadWriteLockTest readWriteLockTest = new ReadWriteLockTest();
        new Thread(() -> {
            readWriteLockTest.test1();
        }).start();
        //让test1方法先执行
        TimeUnit.SECONDS.sleep(2);

        new Thread(() -> {
            readWriteLockTest.test2();
        }).start();
    }

一般情况下读操作配读锁,写操作配写锁。
在写操作的情况下,如果写完之后 不去使用了可以直接释放写锁

如果写完之后,还要继续使用这些状态(状态量可能有多个),如果直接释放写锁,导致其他线程修改了这些状态,那么之后的操作感知不到其他线程的修改,(比如:
writeLock.lock();
User user=new User();
map.put(“user1”,user);
writeLock.unlock();
String userName=user.getUserName(); //此时还一直使用以前的值感知不到其他线程的修改)

所以此时加上一个读锁
writeLock.lock();
User user=new User();
map.put(“user1”,user);
//降级
readLock.lock();
writeLock.unlock();

String userName=user.getUserName(); //此时其他加锁线程可以读到map中的user,但是不能去修改。所以此时得到的userName依然是正确的,而且在读多写少的情况下回大幅降低锁竞争带来的消耗
readLock.unlock();

你可能感兴趣的:(多线程)