深入理解ReentrantReadWriteLock

全文概要

本文将继续讲述线程并发库,ReentrantReadWriteLock是本文的主要介绍对象。顾名思义,ReentrantReadWriteLock为可重入的读写锁。使用时,读取数据的时候上读锁,写数据的时候上写锁读锁与读锁之间不互斥,写锁与写锁之间互斥,读锁与写锁之间互斥,这样就比synchronized的设计效率更加高明,能够最大限度的利用CPU资源解决问题。本文的主要内容如下:

  1. 简单介绍ReentrantReadWriteLock的API;
  2. 通过几个案例说明ReentrantReadWriteLock的用法;
  3. 通过JDK官方文档上ReentrantReadWriteLock的一个缓存例子说明读写锁的使用场景。

ReentrantReadWriteLock说明

ReentrantReadWriteLock实现了ReadWriteLock接口,该类中有两个重要的方法readLock()/writeLock()。readLock()获取的是一个读锁,他是一个共享锁,即在相同的时刻,可以被多个线程获取到,而writeLock()获取的是写一个写锁,他是一个独占锁,在同一时刻只能被一个线程获取。

ReentrantReadWriteLock案例

使用synchronized来模拟多个线程读取数据:

  • 代码案例
package com.tml.javaCore.concurrent.readWriteLock;
/**
 * 

使用synchronized来模拟多个线程读取文件数据 * @author Administrator * */ public class ReadWriteLockDemo1 { public static void main(String[] args) { ReadWriteLockDemo1 lock = new ReadWriteLockDemo1(); new Thread(new Runnable() { public void run() { lock.readFile(); } }, "thread_01").start(); new Thread(new Runnable() { public void run() { lock.readFile(); } }, "thread_02").start(); } private synchronized void readFile(){ System.out.println(Thread.currentThread().getName() + " is ready!"); for(int i=0;i<6;i++){ System.out.println(Thread.currentThread().getName() + " is reading......"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + " is end!"); } }

  • 结果输出
    thread_01 is ready!
    thread_01 is reading......
    thread_01 is reading......
    thread_01 is reading......
    thread_01 is reading......
    thread_01 is reading......
    thread_01 is reading......
    thread_01 is end!
    thread_02 is ready!
    thread_02 is reading......
    thread_02 is reading......
    thread_02 is reading......
    thread_02 is reading......
    thread_02 is reading......
    thread_02 is reading......

    thread_02 is end!

  • 结果分析

两个线程调用互斥的readFile()方法,从结果来分析得知,两个线程同时读取文件的时候是互斥的,即在同一时刻,只能有一个线程获取readFile()方法的执行权,这样虽然做到了互斥,但是效率就没有那么高效,下面一个例子是使用并发库中的读锁来实现。

  • 代码案例
package com.tml.javaCore.concurrent.readWriteLock;

import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 

使用ReentrantReadWriteLock来模拟多个线程读取文件 * @author Administrator * */ public class ReadWriteLockDemo3 { ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public static void main(String[] args) { ReadWriteLockDemo3 lock = new ReadWriteLockDemo3(); new Thread(new Runnable() { public void run() { lock.readFile(); } }, "thread_01").start(); new Thread(new Runnable() { public void run() { lock.readFile(); } }, "thread_02").start(); } private void readFile(){ //获取读锁,上锁 readWriteLock.readLock().lock(); try{ System.out.println(Thread.currentThread().getName() + " is ready!"); for(int i=0;i<6;i++){ System.out.println(Thread.currentThread().getName() + " is reading......"); Thread.sleep(100); } System.out.println(Thread.currentThread().getName() + " is end!"); }catch(InterruptedException e){ e.printStackTrace(); }finally { readWriteLock.readLock().unlock(); } } }

  • 结果输出
    thread_01 is ready!
    thread_01 is reading......
    thread_02 is ready!
    thread_02 is reading......
    thread_01 is reading......
    thread_02 is reading......
    thread_01 is reading......
    thread_02 is reading......
    thread_01 is reading......
    thread_02 is reading......
    thread_02 is reading......
    thread_01 is reading......
    thread_01 is reading......
    thread_02 is reading......
    thread_01 is end!

    thread_02 is end!

  • 结果分析
  1. 线程进入readFile()方法中,首先会获取读锁,并加锁,执行完方法后,会在finally中释放读锁,注意加锁释放锁的代码规范都是,try-catch-finally;
  2. 由于读锁是共享锁,即在同一时刻,可以被多个线程获取到,在主线程中创建了两个线程,这两个线程是同步执行的,相比synchronized效率更佳。
  • 代码案例

下面一个案例是介绍ReentrantReadWriteLock中读写锁的区别:

package com.tml.javaCore.concurrent.readWriteLock;

import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 

使用ReentrantReadWriteLock来模拟多个线程读取文件和读个线程写文件 * @author Administrator * */ public class ReadWriteLockDemo2 { ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public static void main(String[] args) { ReadWriteLockDemo2 lock = new ReadWriteLockDemo2(); new Thread(new Runnable() { public void run() { lock.readFile(); } }, "thread_01").start(); new Thread(new Runnable() { public void run() { lock.readFile(); } }, "thread_02").start(); new Thread(new Runnable() { public void run() { lock.writeFile(); } }, "thread_03").start(); new Thread(new Runnable() { public void run() { lock.writeFile(); } }, "thread_04").start(); } private void readFile(){ //获取读锁,上锁 readWriteLock.readLock().lock(); try{ System.out.println(Thread.currentThread().getName() + " is ready!"); for(int i=0;i<6;i++){ System.out.println(Thread.currentThread().getName() + " is reading......"); Thread.sleep(100); } System.out.println(Thread.currentThread().getName() + " is end!"); }catch(InterruptedException e){ e.printStackTrace(); }finally { //释放读锁 readWriteLock.readLock().unlock(); } } private void writeFile(){ //获取写锁,上锁 readWriteLock.writeLock().lock(); try{ System.out.println(Thread.currentThread().getName() + " is ready!"); for(int i=0;i<6;i++){ System.out.println(Thread.currentThread().getName() + " is writing......"); Thread.sleep(100); } System.out.println(Thread.currentThread().getName() + " is end!"); }catch(InterruptedException e){ e.printStackTrace(); }finally { readWriteLock.writeLock().unlock(); } } }

  • 结果输出
    thread_01 is ready!
    thread_01 is reading......
    thread_02 is ready!
    thread_02 is reading......
    thread_01 is reading......
    thread_02 is reading......
    thread_01 is reading......
    thread_02 is reading......
    thread_02 is reading......
    thread_01 is reading......
    thread_01 is reading......
    thread_02 is reading......
    thread_01 is reading......
    thread_02 is reading......
    thread_01 is end!
    thread_02 is end!
    thread_03 is ready!
    thread_03 is writing......
    thread_03 is writing......
    thread_03 is writing......
    thread_03 is writing......
    thread_03 is writing......
    thread_03 is writing......
    thread_03 is end!
    thread_04 is ready!
    thread_04 is writing......
    thread_04 is writing......
    thread_04 is writing......
    thread_04 is writing......
    thread_04 is writing......
    thread_04 is writing......

    thread_04 is end!

  • 结果分析
  1. 主线程中创建了四个线程,线程1和线程2用于读取数据,线程3和线程4用于写入数据;
  2. 从结果分析得知,读锁与读锁之间是共享的,读锁与写锁之间是互斥的,写锁与写锁之间也是互斥的。

ReentrantReadWriteLock应用

ReentrantReadWriteLock使用场景比较多,下面是取自JDK官方文档上的一个应用案例,伪代码如下:

 class CachedData {
   Object data;
   volatile boolean cacheValid;
   ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

   void processCachedData() {
     rwl.readLock().lock();
     if (!cacheValid) {
        // Must release read lock before acquiring write lock
        rwl.readLock().unlock();
        rwl.writeLock().lock();
        // Recheck state because another thread might have acquired
        //   write lock and changed state before we did.
        if (!cacheValid) {
          data = ...
          cacheValid = true;
        }
        // Downgrade by acquiring read lock before releasing write lock
        rwl.readLock().lock();
        rwl.writeLock().unlock(); // Unlock write, still hold read
     }

     use(data);
     rwl.readLock().unlock();
   }
 }
  • 应用案例分析
  1. 这是一个缓存数据的应用案例,data为共享数据;cacheValid是一个标志位,表示用户想获取的数据是否在缓存中,其中用volatile修饰,是为了在多线程并发下,多个线程获取的cacheValid的值相同,rwl是一个读写锁对象;
  2. 用户从缓存中获取数据的时候,首先加上读锁,判断缓存中是否存在用户想获取的数据;
  3. 若缓存中存在用户想要的数据,则获取数据进行处理,即执行use(data)方法,该方法执行完毕后,释放读锁;
  4. 若缓存中不存在用户想要的数据,那么则先释放读锁,然后上写锁,上锁后还需要重新判断缓存中是否有用户想要的数据,因为在释放读锁添加写锁的过程,可能有其他线程的请求;
  5. 若此时,缓存中已经有数据了,因为需要读取数据,则加上读锁,然后释放掉写锁;
  6. 若此时,缓存中仍然没有数据,那就需要该线程从数据库或其他地方获取数据,将数据添加进缓存中,接着将标志位改成true,最后加上读锁,释放掉写锁;
  7. 缓存中已经存在用户想要的数据,则执行use(data)方法使用数据,最后释放掉刚刚添加的读锁,整个过程完毕。

你可能感兴趣的:(多线程,readLock,writeLock,读写锁,缓存系统)