【并发技术12】线程锁技术的使用(二)

简介: 【并发技术12】线程锁技术的使用

2.2 读写锁用于缓存数据


现在使用读写锁写一个模拟缓存数据的 demo,实现功能如下:现在有5个线程都需要拿数据,一开始是没有数据的,所以最先去拿数据的那个线程发现没数据,它就得去初始化一个数据,然后其他线程拿数据的时候就可以直接拿了。代码如下。

public class ReadWriteLockTest2 {
   public static void main(String[] args) {
       CacheData cache = new CacheData();
       for(int i = 1; i <= 5; i ++) { //开启5个线程
           new Thread(new Runnable() {
               @Override
               public void run() {
                   cache.processCache(); //都去拿数据
               }
           }).start();
       }
   }
}
class CacheData {
   private Object data = null; // 需要缓存的数据
   private boolean cacheValid; //用来标记是否有缓存数据
   private ReadWriteLock rwl = new ReentrantReadWriteLock();// 定义读写锁
   public void processCache() {
       rwl.readLock().lock(); //上读锁
       if(!cacheValid) { //如果没有缓存,那说明是第一次访问,需要给data赋个值
           rwl.readLock().unlock(); //先把读锁释放掉
           rwl.writeLock().lock(); //上写锁
           if(!cacheValid) {
               System.out.println(Thread.currentThread().getName() + ": no cache!");
               data = new Random().nextInt(1000); //赋值
               cacheValid = true; //标记已经有缓存了
               System.out.println(Thread.currentThread().getName() + ": already cached!");
           }
           rwl.readLock().lock(); //再把读锁上上
           rwl.writeLock().unlock(); //把刚刚上的写锁释放掉
       }
       System.out.println(Thread.currentThread().getName() + " get data: " + data);
       rwl.readLock().unlock(); //释放读锁
   }
}


从代码中可以看出,在 processCache 方法中对读锁和写锁的交替使用。一开始进来都是读数据的,所以一开始都是上了读锁,但是当第一个线程进来发现没有缓存数据的时候,它得写数据,那么此时它得先把读锁给释放掉,换了把写锁,告诉其他线程:“哥们,这里面根本没数据啊,我们被坑了,让我先弄个数据来吧,你们等会儿~”,等该线程初始化好了数据后,其他线程就可以读了,于是它又把读锁装起来了,把写锁释放了,然后它出去了。这就模拟了拿缓存数据的一个 demo,可以看出,在一个方法中,同一个线程可以操作两个锁的。看一下运行结果。

Thread-1: no cache!

Thread-1: already cached!

Thread-1 get data: 893

Thread-0 get data: 893

Thread-2 get data: 893

这和 Hibernate 中的那个 load(id, Class.class) 方法有点类似,先拿到的是代理对象,要使用该对象的时候,如果发现没有,就新产生一个,如果有了就直接拿来用。


2.3 读写锁用于缓存系统


继续进阶,如果现在要缓存多个数据,即要写一个缓存系统,那该如何做呢?一个缓存系统无非就是一个容器,可以存储很多缓存数据,很自然的想到使用一个 Map,专门装缓存数据,然后供多个线程去使用。所以整个涉及思路,跟上面缓存单个数据是一样的,不过就是多考了一些东西而已,看下代码。

public class CacheDemo {
   public static void main(String[] args) {
       Cache cac = new Cache();
       for(int i = 0; i < 3; i ++) { //开启三个线程去缓存中拿key为cache1的数据,
           new Thread(new Runnable() {
               @Override
               public void run() {
                   String value = (String) cac.getData("cache1"); //第一个进入的线程要先写一个数据进去(相当于第一次从数据库中取)    
                   System.out.println(Thread.currentThread().getName() + ": " + value);    
               }
           }).start();
       }
       for(int i = 0; i < 3; i ++) { //开启三个线程去缓存中拿key为cacahe2的数据
           new Thread(new Runnable() {
               @Override
               public void run() {
                   String value = (String) cac.getData("cache2");//第一个进入的线程要先写一个数据进去(相当于第一次从数据库中取)
                   System.out.println(Thread.currentThread().getName() + ": " + value);
               }
           }).start();
       }
   }
}
class Cache {
   //存储缓存数据的Map,注意HashMap是非线程安全的,也要进行同步操作
   private Map<String, Object> cache = Collections.synchronizedMap(new HashMap<String, Object>());
   private ReadWriteLock rwl = new ReentrantReadWriteLock(); //定义读写锁
   public synchronized Object getData(String key) {
       rwl.readLock().lock(); //上读锁
       Object value = null;
       try {
           value = cache.get(key); //根据key从缓存中拿数据
           if (value == null) { //如果第一次那该key对应的数据,拿不到
               rwl.readLock().unlock(); //释放读锁
               rwl.writeLock().lock(); //换成写锁
               try {
                   if (value == null) { //之所以再去判断,是为了防止几个线程同时进入了上面那个if,然后一个个都来重写赋值一遍
                       System.out.println(Thread.currentThread().getName() + " write cache for " + key);
                       value = "aaa" + System.currentTimeMillis(); // 实际中是去数据库中取,这里只是模拟
                       cache.put(key, value); //放到缓存中
                       System.out.println(Thread.currentThread().getName() + " has already written cache!");
                   }
               } finally {
                   rwl.writeLock().unlock(); //写完了释放写锁
               }
               rwl.readLock().lock(); //换读锁
           }
       } finally {
           rwl.readLock().unlock(); //最后呢释放读锁
       }
       return value; //返回要取的数据
   }
}


整个代码的结构和上面的一样,理解了缓存单个数据后,这个代码也不难理解。这里只是个 demo,实际中可以是跟数据库打交道,第一次从缓存中拿肯定是没有的,那么就要去数据库中查,然后把取到的数据放到缓存中,下次别的线程来就能直接从缓存中取了。看一下运行结果。

Thread-0 write cache for cache1

Thread-0 has already written cache!

Thread-4 write cache for cache2

Thread-0: aaa1464782404722

Thread-4 has already written cache!

Thread-4: aaa1464782404723

Thread-3: aaa1464782404723

Thread-2: aaa1464782404722

Thread-1: aaa1464782404722

Thread-5: aaa1464782404723

从结果中可以看出,线程 0 首先去缓存中拿 key 为 cache1 的值,没拿到,往里面写了一个,然后线程 4 去缓存中拿 key 为 cache2 的值也没拿到,于是也写了一个,在此期间线程 0 把值拿了出来,后面几个线程也随后陆续的拿出来了。读写锁的应用还是很广泛的,而且很好用,就总结这么多吧。


相关文章
|
1月前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
155 0
|
22天前
|
安全
List并发线程安全问题
【10月更文挑战第21天】`List` 并发线程安全问题是多线程编程中一个非常重要的问题,需要我们认真对待和处理。只有通过不断地学习和实践,我们才能更好地掌握多线程编程的技巧和方法,提高程序的性能和稳定性。
128 59
|
13天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
1月前
|
Java 数据库连接 数据库
不同业务使用同一个线程池发生死锁的技术探讨
【10月更文挑战第6天】在并发编程中,线程池是一种常用的优化手段,用于管理和复用线程资源,减少线程的创建和销毁开销。然而,当多个不同业务场景共用同一个线程池时,可能会引发一系列并发问题,其中死锁就是最为严重的一种。本文将深入探讨不同业务使用同一个线程池发生死锁的原因、影响及解决方案,旨在帮助开发者避免此类陷阱,提升系统的稳定性和可靠性。
48 5
|
1月前
|
Java
【编程进阶知识】揭秘Java多线程:并发与顺序编程的奥秘
本文介绍了Java多线程编程的基础,通过对比顺序执行和并发执行的方式,展示了如何使用`run`方法和`start`方法来控制线程的执行模式。文章通过具体示例详细解析了两者的异同及应用场景,帮助读者更好地理解和运用多线程技术。
26 1
|
1月前
|
Java 应用服务中间件 测试技术
Java21虚拟线程:我的锁去哪儿了?
【10月更文挑战第8天】
32 0
|
1月前
|
网络协议 安全 Java
难懂,误点!将多线程技术应用于Python的异步事件循环
难懂,误点!将多线程技术应用于Python的异步事件循环
61 0
|
1月前
|
安全 调度 数据安全/隐私保护
iOS线程锁
iOS线程锁
26 0
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
43 1
C++ 多线程之初识多线程
|
24天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
19 3

相关实验场景

更多