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

来源:互联网 发布:windows程序设计第5版 编辑:程序博客网 时间:2024/05/28 04:55

基本介绍:


读写锁: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 blocke.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
原创粉丝点击