Java锁之ReentrantReadWriteLock

概述 

ReentrantReadWriteLock是Lock的另一种实现方式,我们已经知道了ReentrantLock是一个排他锁,同一时间只允许一个线程访问,而ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。相对于排他锁,提高了并发性。在实际应用中,大部分情况下对共享数据(如缓存)的访问都是读操作远多于写操作,这时ReentrantReadWriteLock能够提供比排他锁更好的并发性和吞吐量。

  读写锁内部维护了两个锁,一个用于读操作,一个用于写操作。所有 ReadWriteLock实现都必须保证 writeLock操作的内存同步效果也要保持与相关 readLock的联系。也就是说,成功获取读锁的线程会看到写入锁之前版本所做的所有更新。

      ReentrantReadWriteLock支持以下功能:

  1. 支持公平和非公平的获取锁的方式;
  2. 支持可重入。读线程在获取了读锁后还可以获取读锁;写线程在获取了写锁之后既可以再次获取写锁又可以获取读锁;
  3. 还允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不允许的;
  4. 读取锁和写入锁都支持锁获取期间的中断;
  5. Condition支持。仅写入锁提供了一个 Conditon 实现;读取锁不支持 Conditon ,readLock().newCondition() 会抛出 UnsupportedOperationException。 

 

独占锁和共享锁

独占锁:指该锁一次只能被一个线程持有

共享锁:该锁可以被多个线程持有

对于 ReentrantLock 和 synchronized 都是独占锁;对与 ReentrantReadWriteLock 其读锁是共享锁而写锁是独占锁。读锁的共享可保证并发读是非常高效的,读写、写读和写写的过程是互斥的。

需求:缓存实现

不加锁:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

//手写一个缓存
class MyCash{	
	private volatile Map map=new HashMap<>();	
	public void put(String key,Object value){		
		System.out.println(Thread.currentThread().getName()+"\t 正在写入...");
		//暂停一会线程
		try { TimeUnit.MILLISECONDS.sleep(300); } catch (Exception e) { e.printStackTrace(); }
		map.put(key, value);
		System.out.println(Thread.currentThread().getName()+"\t 写入完成:"+value);					
	}
	public void get(String key){		
		System.out.println(Thread.currentThread().getName()+"\t 正在读取...");
		//暂停一会线程
		try { TimeUnit.MILLISECONDS.sleep(300); } catch (Exception e) { e.printStackTrace(); }
		Object result=map.get(key);
		System.out.println(Thread.currentThread().getName()+"\t 读取完成:"+result);					
	}
}

public class ReentrantReadWriteLockDemo {
	public static void main(String[] args) {	
		MyCash myCash=new MyCash();
		for (int i = 1; i <= 5; i++) {
			final int temp=i;
			new Thread(()->{
				myCash.put(temp+"", temp+"");
			},"线程T"+String.valueOf(i)).start();
		}		
		for (int i = 1; i <= 5; i++) {
			final int temp=i;
			new Thread(()->{
				myCash.get(temp+"");
			},"线程T"+String.valueOf(i)).start();
		}		
	}
}

不加锁运行效果:

Java锁之ReentrantReadWriteLock_第1张图片

不加锁,我们会看到读操作在写操作前面,出现了null值的情况。 

加读写锁

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

//手写缓存
class MyCash{
	private volatile Map map=new HashMap<>();
	private ReentrantReadWriteLock rrwLock=new ReentrantReadWriteLock();	
	public void put(String key,Object value){
		rrwLock.writeLock().lock();
		try {			
			System.out.println(Thread.currentThread().getName()+"\t 正在写入...");
			//暂停一会线程
			try { TimeUnit.MILLISECONDS.sleep(300); } catch (Exception e) { e.printStackTrace(); }
			map.put(key, value);
			System.out.println(Thread.currentThread().getName()+"\t 写入完成:"+value);			
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			rrwLock.writeLock().unlock();
		}	
	}
	public void get(String key){
		rrwLock.readLock().lock();
		try {			
			System.out.println(Thread.currentThread().getName()+"\t 正在读取...");
			//暂停一会线程
			try { TimeUnit.MILLISECONDS.sleep(300); } catch (Exception e) { e.printStackTrace(); }
			Object result=map.get(key);
			System.out.println(Thread.currentThread().getName()+"\t 读取完成:"+result);	
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			rrwLock.readLock().unlock();
		}	
	}
}
/**
 * 读写锁ReentrantReadWriteLock:
 * 
 * 多个线程同时读取一个资源类没有问题,所以为了提高并发量,读取资源类应该同时进行
 * 但是,如果一个线程如果去写一个共享资源,就不应该再有其他线程对该资源读或者写
 * 总结:
 * 		读读共存-读写互斥-写写互斥
 * 写操作:原子+独占
 * 
 * @author kavito
 */
public class ReentrantReadWriteLockDemo {
	public static void main(String[] args) {		
		MyCash myCash=new MyCash();
		for (int i = 1; i <= 5; i++) {
			final int temp=i;
			new Thread(()->{
				myCash.put(temp+"", temp+"");
			},"线程T"+String.valueOf(i)).start();
		}		
		for (int i = 1; i <= 5; i++) {
			final int temp=i;
			new Thread(()->{
				myCash.get(temp+"");
			},"线程T"+String.valueOf(i)).start();
		}		
	}
}

运行效果:

Java锁之ReentrantReadWriteLock_第2张图片

你可能感兴趣的:(多线程)