JDK1.8源码学习篇三——读写锁ReentrantReadWriteLock学习笔记

一、引言

    之前学习了java锁的相关概念,从最开始的大家使用是synchronized关键字,这个重量级锁,性能非常的低下,但是在jdk1.6之后经过优化之后, 性能大幅提升。但是在jdk1.5上新增加的锁lock性能和功能都大幅提升,被大家广泛采用。在上一篇文章中也学习了关于同步的一些基础构建AQS,同时在此基础上也学习了一下java同步中常用到的独占锁ReentrantLock的源码,从宏观上深入了解了一下独占锁的原理。独占锁,在使用的过程中 同一时间,只允许一个线程去获取锁,其他线程只能在等待状态,这样做的并发的安全性是提高了,但是在某些场景下,性能也降低了。举个例子,在日常生活中,读取数据的操作远远高于写操作的频率,但是如果都采用独占锁的,只能一个等待一个进行等待获取锁,然后读取数据。因此jdk中又提出了共享锁的概念,比如读锁,多个线程可以获取读锁,同一个时间去读操作,这个类就是ReentrantReadWriteLock。这是一个读写锁,分别包含读锁和写锁。读写锁在同一个是时刻允许多个线程访问,因此读锁是共享锁,但是在写锁只允许一个线程访问,是排他锁。

 二、ReentrantReadWriteLock详解

    
    在jdk1.5的并发包中,总共提出两种锁的类型,一种是共享锁,一种是排他锁。比如之前学习的ReentrantLock就是排他锁,在同一时刻,只能一个线程拥有锁,其他线程如果想要获取锁只能进入等待队列中,等待当前线程释放锁,唤醒下一个等待的线程。而共享锁指的是,在某一时刻可以有多个线程来获取锁,这种场景在生活中比较常见,比如读,多个线程可以同一时刻去读取数据,但是却不能同时写。ReentrantReadWriteLock其实是一种混合形式的锁,即包含了共享锁页包含了排他锁。这样做的目的是为了适应实际场景中一种业务,对于同一块数据,或者同一个对象,即存在需要读取的时候,也存在需要修改的时候,最常见的是缓存(cache)。如果对于这种场景,如果只用共享锁,允许多个线程读取或者写入数据,那么数据操作的原子性就无法得到保证在多并发的情况下,数据往往就是混乱的;而如果只是用排他锁,即在某一个时刻,只能允许某个线程读取操作,这样的的确是可以保证数据的原子性操作,但是却极大的降低了性能,因为很多时候多个线程可以一起读取的。所以在兼容以上场景下,即保证数据操作的原子性,又要保证性能的情况下,ReentrantReadWriteLock这种混合锁就显得非常重要了。读写锁也可以成为共享锁,毕竟读是可以同时操作的,一般的“读-读”可以共享锁,“读-写”、“写-写”都只能是排他锁。
 
  1. ReentrantReadWriteLock特性 
    先说说读写锁的特性:
    非公平锁:由于读线程之间不存在竞争性,所有没啥公平和非公平的区分,但是写操作,如果获取到写锁,其他线程和读锁就必须等待。
    公平锁:利用AQS的CLH队列,释放当前保持的锁(读锁或者写锁)时,优先为等待时间最长的那个写线程分配写入锁,当前提是写线程的等待时间要比所有读线程的等待时间要长。同样一个线程持有写入锁或者有一个写线程已经在等待了,那么试图获取公平锁的(非重入)所有线程(包括读写线程)都将被阻塞,直到最先的写线程释放锁。如果读线程的等待时间比写线程的等待时间还有长,那么一旦上一个写线程释放锁,这一组读线程将获取锁。
     重入锁: 读写锁允许读线程和写线程按照请求锁的顺序重新获取读锁或者写锁,当然只有写线程释放了锁,读线程才能获取锁,这就是重入性。
     降级锁:比如说写线程获取到写锁,写完之后,释放了写入锁,这样就变成了读锁,实现了锁的降级。
     锁升级:读锁是不能直接变成写锁的,因此要先释放读锁,然后获取写锁,这样就变成了锁升级。
     锁数量:读写所采用的是32位的二进制来保存锁的数量,其中高位保存读锁,低位报错血锁,因此锁的数量最大只能65535.

  2. 读写锁的使用
     说了这么多的读写锁的概念,下面先看看读写锁的使用场景,在经常使用到的缓存cache中就要用到读写锁,因为大量线程都要从缓存中读取数据,但是

     同时也要更新数据到缓存中,因此必然用到读写锁。下面我们先看个例子。

public class Cache {

	private final ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
	Map<Integer, String> cache = new HashMap<Integer,String>();	  //缓存的容器,来存储数据
	private int num = 1024;	
	public Cache(){
		cache.put(1,(int)Math.random()*10+"");
		cache.put(2,(int)Math.random()*10+"");
		cache.put(3,(int)Math.random()*10+"");
	}
	public void put(Integer key){
		int value = (int) (Math.random()*1000);
		cache.put(key,value+"");
	}
	
	public Object get(Integer key){
		ReadLock rl = rwlock.readLock();   //读锁
		WriteLock wl = rwlock.writeLock();  //写锁
		Object obj = null;
		try {
			rl.lock();//获取读锁
		    obj = cache.get(key);
			if(obj==null){
				//缓存不存在
				rl.unlock();
				try{
					wl.lock();   //相当于锁升级,从读锁升级到写锁
					this.put(key);  //如果数据不在缓存中,添加到缓存,当然这里应该有个策略,定时清理缓存,否则所有的数据都到缓存中去了
				}catch(Exception e){
					e.printStackTrace();
				}finally{
				   obj = cache.get(key);
				   wl.unlock();//
				   rl.lock();//说明读锁已经释放,需要再次获取锁 锁降级,从写锁降级到读锁
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			rl.unlock();
		}
		return cache.get(key);
	}
	

下面这个是实现的一个线程,为了能够得到返回结果,这里实现了Callable接口,

public class Task implements Callable<String> {
  
	private Cache cache;
	
	public Task(Cache cache) {
		
		th
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值