java读写锁ReentrantReadWriteLock实现多并发单利模式

基本介绍:


读写锁:ReadWriteLock
 
       在多线程的环境下,对同一份数据进行读写,会涉及到线程安全的问题。比如在一个线程读取数据的时候,另外一个线程在写数据,而导致前后数据的不一致性;一个线程在写数据的时候,另一个线程也在写,同样也会导致线程前后看到的数据的不一致性。
 
       这时候可以在读写方法中加入互斥锁,任何时候只能允许一个线程的一个读或写操作,而不允许其他线程的读或写操作,这样是可以解决这样以上的问题,但是效率却大打折扣了。因为在真实的业务场景中,一份数据,读取数据的操作次数通常高于写入数据的操作,而线程与线程间的读读操作是不涉及到线程安全的问题,没有必要加入互斥锁,只要在读-写,写-写期 间上锁就行了。

 对于这种情况,读写锁则最好的解决方案!


ReentrantReadWriteLock中定义了2个内部类,ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock,分别用来代表读取锁和写入锁,ReentrantReadWriteLock对象提供了readLock()和writeLock()方法,用于获取读取锁和写入锁


其中:
  • 读取锁允许多个reader线程同时持有,而写入锁最多只能有一个writer线程持有。
  • 读写锁的使用场合是:读取数据的频率远大于修改共享数据的频率。在上述场合下使用读写锁控制共享资源的访问,可以提高并发性能。
  • 如果一个线程已经持有了写入锁,则可以再持有读写锁。相反,如果一个线程已经持有了读取锁,则在释放该读取锁之前,不能再持有写入锁。
  • 可以调用写入锁的newCondition()方法获取与该写入锁绑定的Condition对象,此时与普通的互斥锁并没有什么区别,但是调用读取锁的newCondition()方法将抛出异常。

两种互斥锁机制:

1、synchronized

2、ReentrantLock

ReentrantLock是jdk5的新特性,采用ReentrantLock可以完全替代替换synchronized传统的锁机制,而且采用ReentrantLock的方式更加面向对象,也更加灵活


读写锁的机制:
   "读-读"不互斥
   "读-写"互斥
   "写-写"互斥
 
 即在任何时候必须保证:
   只有一个线程在写入;
   线程正在读取的时候,写入操作等待;
   线程正在写入的时候,其他线程的写入操作和读取操作都要等待;

锁降级:从写锁变成读锁;锁升级:从读锁变成写锁。读锁是可以被多线程共享的,写锁是单线程独占的。也就是说写锁的并发限制比读锁高


如下代码会产生死锁,因为同一个线程中,在没有释放读锁的情况下,就去申请写锁,这属于锁升级,ReentrantReadWriteLock是不支持的。

ReadWriteLock rtLock = new ReentrantReadWriteLock();
		rtLock.readLock().lock();
		System.out.println("get readLock.");
		rtLock.writeLock().lock();
		System.out.println("blocking");</span></span>



ReentrantReadWriteLock支持锁降级,如下代码不会产生死锁。

ReadWriteLock rtLock = new ReentrantReadWriteLock();
		rtLock.writeLock().lock();
		System.out.println("writeLock");
		rtLock.readLock().lock();
		System.out.println("get read lock");


这段代码虽然不会导致死锁,但没有正确的释放锁。从写锁降级成读锁,并不会自动释放当前线程获取的写锁,仍然需要显示的释放,否则别的线程永远也获取不到写锁。锁的释放和获取可以看下:可重入锁的获取和释放需要注意的一点儿事

<span style="white-space:pre">	</span>final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
		Thread wt = new Thread(new Runnable(){
			public void run(){	
				readWriteLock.writeLock().lock();	
				System.out.println("writeLock");	
				readWriteLock.readLock().lock();	
				System.out.println("readLock");	
				readWriteLock.readLock().unlock();	
				System.out.println("block");
				}
			});
				wt.start();
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			System.out.println("main blocking.");
			readWriteLock.readLock().lock();</span>




应用场景:

读写锁的适用场景 
读多写少的高并发环境下,可以说这个场景算是最适合使用ReadWriteLock 了。 


单利模式代码:


import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Singleton {
	 private static Singleton instance = null;  
	    private static ReadWriteLock rwl = new ReentrantReadWriteLock();  
	    private Singleton(){  
	    }  
	     /**
	      * 当前线程在获取到写锁的过程中,可以获取到读锁,这叫锁的重入,然后导致了写锁的降级,称为降级锁。 从 3 获得写锁 到 5变成读锁 降级
	                 利用重入可以将写锁降级,但只能在当前线程保持的所有写入锁都已经释放后,才允许重入 reader使用它们。  6
	                 所以在重入的过程中,其他的线程不会有获取到锁的机会(这样做的好处)。试想,先释放写锁,在上读锁,这样做有什么弊端?(如果5和6颠倒)
	                  如果这样做,那么在释放写锁后,在得到读锁前,有可能被其他线程打断。
	                  重入————>降级锁的步骤:先获取写入锁3,然后获取读取锁5,最后释放写入锁6(重点)
	      * @return
	      */
	    public static Singleton getInstance(){  
	        rwl.readLock().lock();  //1
	        try  
	        {  
	            if (null == instance)  
	            {  
	                rwl.readLock().unlock();  //2
	                rwl.writeLock().lock();  //3
	                if (null == instance)  //4
	                {  
	                    instance = new Singleton();  
	                }  
	                rwl.readLock().lock();  //5
	                rwl.writeLock().unlock();  //6
	            }  
	        }  
	        finally  
	        {  
	            rwl.readLock().unlock();  //7
	        }  
	        return instance;  
	    }  
}


代码分析:

 当有n多线程 使用同Singleton 实例对象 调用getInstance方法时,就会产生线程的并发问题.
   @1行,当有线程正在对数据进行 写操作的时候,运行到@1行的线程要等待 写操作的完成,因为第一个运行到@3的线程会加上锁,然后对数据进行需该,期间不允许任何线程进行读或者是写的操作,


   当写完后,在该线程上加上读锁操作,以防止解写锁后,别的线程对数据再次进行写时出错.在第一个运行到@3的线程之后的很多线程,


   可能已经运行到了@2,当对数据修改好之后,解除掉写锁,别的线程就会执行到@3,这时第一个线程已经经数据修改好了,所以有了@4的判断。


   在编写多线程程序的时候,要置于并发线程的环境下考虑,巧妙的运用ReentrantReadWriteLock,在运用时,注意锁的降级,写入锁可以获得读锁,读锁不可以获得写入锁,所以在上写入锁时,必须先将读锁进行解除,然后上读锁。


   使用时注意的几个方面:
       读锁是排写锁操作的,读锁不排读锁操作,多个读锁可以并发不阻塞。即在读锁获取后和读锁释放之前,写锁并不能被任何线程获得,
       多个读锁同时作用期间,试图获取写锁的线程都处于等待状态,当最后一个读锁释放后,试图获取写锁的线程才有机会获取写锁。
       写锁是排写锁、排读锁操作的。当一个线程获取到写锁之后,其他试图获取写锁和试图获取读锁的线程都处于等待状态,直到写锁被释放。
         写锁是可以获得读锁的,即:
         rwl.writeLock().lock();
         //在写锁状态中,可以获取读锁
         rwl.readLock().lock();
         rwl.writeLock().unlock();
     读锁是不能够获得写锁的,如果要加写锁,本线程必须释放所持有的读锁,即:
          rwl.readLock().lock();
         //......
         //必须释放掉读锁,才能够加写锁
         rwl.readLock().unlock();
         rwl.writeLock().lock();


当前线程在获取到写锁的过程中,可以获取到读锁,这叫锁的重入,然后导致了写锁的降级,称为降级锁。 

从 3 获得写锁 到 5变成读锁降级
利用重入可以将写锁降级,但只能在当前线程保持的所有写入锁都已经释放后,才允许重入 reader使用它们。  6
所以在重入的过程中,其他的线程不会有获取到锁的机会(这样做的好处)。试想,先释放写锁,在上读锁,这样做有什么弊端?(如果5和6颠倒)
如果这样做,那么在释放写锁后,在得到读锁前,有可能被其他线程打断。
重入————>降级锁的步骤:先获取写入锁3,然后获取读取锁5,最后释放写入锁6(重点)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值