多线程之使用读写锁ReentrantReadWriteLock实现缓存系统

简单地缓存系统:当有线程来取数据时,如果该数据存在我的内存中,我就返回数据;如果不存在我的缓存系统中,那么就去查数据库,返回数据的同时保存在我的缓存中。

其中涉及到读写问题:当多个线程执行读操作时(都加读锁),如果有数据返回;如果没有数据时,则让第一个读的线程,进行获取数据,然后进行写操作,这时需要第一个线程先释放掉读锁然后加写锁。第一个写完后,在家读锁,其他线程使用时判断,如果存在该数据,在直接过去读取不用加写锁。

API上缓存例子如下:

 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();
   }
 }
ReentrantReadWriteLock

此类具有以下属性:

  • 获取顺序

    此类不会将读取者优先或写入者优先强加给锁访问的排序。但是,它确实支持可选的公平 策略。

    非公平模式(默认)
    当非公平地(默认)构造时,未指定进入读写锁的顺序,受到 reentrancy 约束的限制。连续竞争的非公平锁可能无限期地推迟一个或多个 reader 或 writer 线程,但吞吐量通常要高于公平锁。

    公平模式
    当公平地构造线程时,线程利用一个近似到达顺序的策略来争夺进入。当释放当前保持的锁时,可以为等待时间最长的单个 writer 线程分配写入锁,如果有一组等待时间大于所有正在等待的 writer 线程 的 reader 线程,将为该组分配写入锁。

    如果保持写入锁,或者有一个等待的 writer 线程,则试图获得公平读取锁(非重入地)的线程将会阻塞。直到当前最旧的等待 writer 线程已获得并释放了写入锁之后,该线程才会获得读取锁。当然,如果等待 writer 放弃其等待,而保留一个或更多 reader 线程为队列中带有写入锁自由的时间最长的 waiter,则将为那些 reader 分配读取锁。

    试图获得公平写入锁的(非重入地)的线程将会阻塞,除非读取锁和写入锁都自由(这意味着没有等待线程)。(注意,非阻塞 ReentrantReadWriteLock.ReadLock.tryLock()ReentrantReadWriteLock.WriteLock.tryLock() 方法不会遵守此公平设置,并将获得锁(如果可能),不考虑等待线程)。

  • 重入

    此锁允许 reader 和 writer 按照 ReentrantLock 的样式重新获取读取锁或写入锁。在写入线程保持的所有写入锁都已经释放后,才允许重入 reader 使用它们。

    此外,writer 可以获取读取锁,但反过来则不成立。在其他应用程序中,当在调用或回调那些在读取锁状态下执行读取操作的方法期间保持写入锁时,重入很有用。如果 reader 试图获取写入锁,那么将永远不会获得成功。

  • 锁降级

    重入还允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不可能的
  • 锁获取的中断

    读取锁和写入锁都支持锁获取期间的中断。
  • Condition 支持

    写入锁提供了一个 Condition 实现,对于写入锁来说,该实现的行为与
    ReentrantLock.newCondition() 提供的 Condition 实现对 ReentrantLock 所做的行为相同。当然,此 Condition 只能用于写入锁。

    读取锁不支持 ConditionreadLock().newCondition() 会抛出
    UnsupportedOperationException

  • 监测

    此类支持一些确定是保持锁还是争用锁的方法。这些方法设计用于监视系统状态,而不是同步控制。

java实现如下:

package andy.thread.test;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author Zhang,Tianyou
 * @version 2014年11月9日 上午9:29:42
 */

public class ThreadCaChe {

	private static Map<String, Object> cacheMap = new HashMap<String, Object>();

	public static void main(String[] args) {

		for (int i = 0; i < 10; i++) {

			new Thread(new Runnable() {

				@Override
				public void run() {

					String obj = (String) getData("andy");
					System.out.println(obj);

				}
			}).start();

		}

	}

	public static Object getData(String key) {

		ReadWriteLock rwlLock = new ReentrantReadWriteLock();

		// 先加读锁
		rwlLock.readLock().lock();
		Object value = null;
		try {
			value = cacheMap.get(key);
			// 若不存在cache中
			if (value == null) {
				// 若果value为空 则释放掉读锁,让该线程获取写锁,而其他线程只能等待该写锁释放,才能在进读锁
				rwlLock.readLock().unlock();
				// 加写锁
				rwlLock.writeLock().lock();

				try {
					if (value == null) {
						// 从数据中获取数据
						value = "andy is shuai ge";// 查询数据库
						// 存入缓存中
						cacheMap.put(key, value);
					}
				} finally {
					rwlLock.writeLock().unlock();
				}

				rwlLock.readLock().lock();
			}

		} finally {
			// 释放第一次获取的读锁
			rwlLock.readLock().unlock();
		}

		return value;
	}
}

执行效果如下:

andy is shuai ge
andy is shuai ge
andy is shuai ge
andy is shuai ge
andy is shuai ge
andy is shuai ge
andy is shuai ge
andy is shuai ge
andy is shuai ge
andy is shuai ge
时间: 2025-01-07 01:11:51

多线程之使用读写锁ReentrantReadWriteLock实现缓存系统的相关文章

JAVA 并发编程-读写锁之模拟缓存系统(十一)

在多线程中,为了提高效率有些共享资源允许同时进行多个读的操作,但只允许一个写的操作,比如一个文件,只要其内容不变可以让多个线程同时读,不必做排他的锁定,排他的锁定只有在写的时候需要,以保证别的线程不会看到数据不完整的文件.这时候就需要使用读写锁. /** * 简单读写锁demo * @author hejingyuan * */ public class ReadWriteLockTest { public static void main(String[] args) { final Queu

Java多线程13:读写锁和两种同步方式的对比

读写锁ReentrantReadWriteLock概述 大型网站中很重要的一块内容就是数据的读写,ReentrantLock虽然具有 完全互斥排他的效果(即同一时间只有一个线程正在执行lock后面的任务),但是效率非常低.所以在JDK中提供了一种读写锁 ReentrantReadWriteLock,使用它可以加快运行效率. 读写锁表示两个锁,一个是读操作相关的锁,称为共享锁:另一个是写操作相关的锁,称为排他锁.我把这两个操作理解为三句话: 1.读和读之间不互斥,因为读操作不会有线程安全问题 2.

读写锁 ReentrantReadWriteLock

一.读写锁 ReadWriteLock概念特点读写锁维护了一对相关的锁,一个用于只读操作,一个用于写入操作.只要没有writer,读取锁可以由多个reader线程同时保持.写入锁是独占的. 互斥锁[ReetrantLock]一次只允许一个线程访问共享数据,哪怕进行的是只读操作:读写锁[ReadWriteLock]允许对共享数据进行更高级别的并发访问:对于写操作,一次只有一个线程(write线程)可以修改共享数据,对于读操作,允许任意数量的线程同时进行读取.writer可以获取读取锁,但reade

被面试官吊打系列之JUC之 可重入读写锁ReentrantReadWriteLock 之 源码详尽分析

可重入读写锁 ReentrantReadWriteLock 其实基本上模拟了文件的读写锁操作.ReentrantReadWriteLock 和ReentrantLock 的差别还是蛮大的: 但是也有很多的相似之处: ReentrantReadWriteLock 的 writerLock 其实就是相当于ReentrantLock,但是它提供更多的细腻的控制:理解什么是读锁.写锁非常重要,虽然实际工作中区分读写锁这样的细分使用场景比较少. ReentrantReadWriteLock 把锁进行了细化

[图解Java]读写锁ReentrantReadWriteLock

图解ReentrantReadWriteLock 如果之前使用过读写锁, 那么可以直接看本篇文章. 如果之前未使用过, 那么请配合我的另一篇文章一起看:[源码分析]读写锁ReentrantReadWriteLock 0. demo 我先给出一个demo, 这样大家就可以根据我给的这段代码, 边调试边看源码了. 还是那句话: 注意"My" , 我把ReentrantReadWriteLock类 改名为了 "MyReentrantReadWriteLock"类 , &q

读写锁ReadWriteLock和缓存实例

读写锁:多个读锁不互斥,读锁与些锁互斥,写锁与写锁互斥.即:读的时候不允许写,写的时候不允许读,可以同时读. synchronized关键字和普通的Lock构造的锁,会造成读与读之间的互斥,因此读写锁可提高性能. 例子1:三个线程同时对一个共享数据进行读写. import java.util.Random; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantRea

深入浅出 Java Concurrency (14): 锁机制 part 9 读写锁 (ReentrantReadWriteLock) (2)[转]

这一节主要是谈谈读写锁的实现. 上一节中提到,ReadWriteLock看起来有两个锁:readLock/writeLock.如果真的是两个锁的话,它们之间又是如何相互影响的呢? 事实上在ReentrantReadWriteLock里锁的实现是靠java.util.concurrent.locks.ReentrantReadWriteLock.Sync完成的.这个类看起来比较眼熟,实际上它是AQS的一个子类,这中类似的结构在CountDownLatch.ReentrantLock.Semapho

Java读写锁(ReentrantReadWriteLock)学习

什么是读写锁 平时,我们常见的synchronized和Reentrantlock基本上都是排他锁,这些锁在同一时刻只允许一个线程进行访问,哪怕是读操作.而读写锁是维护了一对锁(一个读锁和一个写锁),通过分离读锁和写锁,使得同一时刻可以允许多个读线程访问,但是在写线程进行访问时,所有的读线程和其他写线程均被阻塞. 读写锁的优点 1. 简化了读写交互场景编程的复杂度: 在常见的开发中,我们经常会定义一个共享的用作缓存的数据结构:比如一个大Map,缓存全部的城市Id和城市name对应关系.这个大Ma

深入浅出 Java Concurrency (14): 锁机制 part 9 读写锁 (ReentrantReadWriteLock) (2)

这一节主要是谈谈读写锁的实现. 上一节中提到,ReadWriteLock看起来有两个锁:readLock/writeLock.如果真的是两个锁的话,它们之间又是如何相互影响的呢? 事实上在ReentrantReadWriteLock里锁的实现是靠java.util.concurrent.locks.ReentrantReadWriteLock.Sync完成的.这个类看起来比较眼熟,实际上它是AQS的一个子类,这中类似的结构在CountDownLatch.ReentrantLock.Semapho