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(重点)
- java读写锁ReentrantReadWriteLock实现多并发单利模式
- 【Java并发】- ReentrantReadWriteLock,读写锁原理
- ReentrantReadWriteLock实现读写锁
- java并发锁ReentrantReadWriteLock读写锁源码分析
- [java并发]读写锁ReentrantReadWriteLock里面的FairSync 和 NonfairSync
- Java并发编程艺术——ReentrantReadWriteLock(读写锁)
- 轻松掌握java读写锁(ReentrantReadWriteLock)的实现原理
- java并发编程(五)--Java中的锁(读写锁ReentrantReadWriteLock)
- 深入浅出Java并发包—读写锁ReentrantReadWriteLock原理分析(二)
- 深入浅出Java并发包—读写锁ReentrantReadWriteLock原理分析(一)
- java实现线程安全的单利模式
- java线程系列---读写锁ReentrantReadWriteLock
- java多线程:ReentrantReadWriteLock读写锁的使用
- Java多线程探究-读写锁ReentrantReadWriteLock
- Java多线程读写锁ReentrantReadWriteLock原理详解
- Java并发之ReentrantReadWriteLock
- java并发中的ReentrantReadWriteLock
- 并发编程--读写锁ReadWriteLock和ReentrantReadWriteLock(一)
- 《Cocos2d学习之路》九、数据存储的几种方式和基本使用
- java--多态3
- !!!Adb 抓不住Genymotion的解决方法
- js导出gridview到excel
- 构建一个Scala程序
- java读写锁ReentrantReadWriteLock实现多并发单利模式
- <CSS设计指南> 学习总结 -- 第一二章
- QT编译出错解决 libQtCore.so: undefined reference to `QInotifyFileSystemWatcherEngine::create()'
- python学习笔记
- <CSS设计指南> 学习总结 -- 第三章
- Python学习笔记 知识点
- 1332: addreviate--中级
- 使用DateFormat显示不同日期格式
- <CSS设计指南>学习总结 -- 第四章