深入理解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;
/**
 * <p>使用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;

/**
 * <p>使用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;

/**
 * <p>使用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)方法使用数据,最后释放掉刚刚添加的读锁,整个过程完毕。

猜你喜欢

转载自blog.csdn.net/tianmlin1/article/details/79383532