JDK5 之后,不但有了Lock,还有了ReadWriteLock,比之前的Synchronized丰富多了。而这几者有什么关联呢,各自应用的场景是什么呢?
先通过下面的小示例来,比较下传统的synchronized与读写锁readwtirelock的,在处理同一缓存对象池是的小区别:
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemoForCache {
public static void main(String[] args){
ReadWriteLockDemoForCache demo = ReadWriteLockDemoForCache.getInstance();
Object bean1 = demo.getBeanForBadWay("xxx"); //得到缓存中的对象
Object bean2 = demo.getBeanForGoodWay("yyy");
}
private static final ReadWriteLockDemoForCache INSTANCE = new ReadWriteLockDemoForCache();
private ReadWriteLockDemoForCache(){}
public static ReadWriteLockDemoForCache getInstance(){
return INSTANCE;
}
private Map<String,Object> cache = new TreeMap<String,Object>();
/**
* 用传统的方式来,实现缓存对象区中,取数据。
* 优先:与整个方法同步比起来,要优越了好多,第一层判断有数据时,可直接并发的返回数据。
* 缺点:当线程一进入到同步块第二层判断后,线程一睡了几毫秒被剥削执行权几毫秒(或在执行查对象中哪里都可只要是还没有cache.put时)。
* 此时正好一线程执行到 cache.get时,发现obj为空,也进入了第一层判断,在第二层判断外等着。
* 这样的话,会造成 另一些线程不必要的进入同步块。
* @param key
* @return
*/
public Object getBeanForBadWay(String key){
Object obj = cache.get(key);
if(null == obj){
synchronized(this){
obj = cache.get(key); //需要重新获取是否已经有对象了。
if(null == obj){ //需要双重判断,有可能多个线程是要的是多一对象,同时进了一层判断。而二层判断可以有效的避免创建多个对象。
Object reVal = queryForDB(key); //伪代码从数据库中查找对象。
obj = reVal;
cache.put(key, reVal); //将对象缓存起来
}
}
}
//doSomeElse user bean //伪代码,可使用use Bean
return obj;
}
private ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock();
/**
*
* 稍微比上面的传统方式来的要优越一点。因为若多线程中,一线程进入需要排它的互斥正在写数据时。
* 其实还是希望最上面的读数据不要进来读,因为可能在没有写入数据前,读出来也是空的,还要进二层判断,等被再次被排它性的互斥,效率便会低点。
* 用了读写锁,就可以很好的解决这个问题,当排它性的互斥在写数据时,读缓存线程会等着,至到被写入后再去读。
* @param key
* @return
*/
public Object getBeanForGoodWay(String key){
Object obj = null ;
//后面再来一个key,执行第一语时,卡住了要等着。因为线程一正在put进去,之后它才有read权,发现已经有了就不会再进第二层判断了。
//这个就是比上面有优越的地方,上面的那种方法此后面的Key是可能在其它线程写时也判断出为空,而进了第二层循环等着,等完获得锁再又去判断了一次。
rrwl.readLock().lock();
try{
//多线程可以并发的读取缓存中的对象。但是在读取时,不可操作obj为null,查数据库put进去后,再可以读。
obj = cache.get(key);
if(null == obj){
rrwl.readLock().unlock(); //若对象是空的,则需要将读锁释放掉,进行写锁。
rrwl.writeLock().lock(); //这里为什么再加个判断,是因为。若线程A进入了写锁,而之前线程B也早进了第一层判断。
//只不过被线程A先进了写锁。A根据需要创建完后,B肯定要再检查次是否有对象了,有了就不创建对象了。
obj = cache.get(key);
if(null ==obj){
Object reVal = queryForDB(key); //伪代码从数据库中查找对象。
obj = reVal;
cache.put(key, reVal); //将对象缓存起来
}
rrwl.readLock().lock(); //锁降级,可以在写锁完蛋前加个读锁。称之为读写锁。这里也必须加个读锁才能在双重判断都进入时,有unlcok可执行。
rrwl.writeLock().unlock();//严格的话,要try finally,用finally把writeLock.unLock包围起来的。
}
//doSomeElse user bean
}finally{
rrwl.readLock().unlock();
}
return obj ;
}
private Object queryForDB(String key) {
//伪代码从数据库中,查找key对应的 对象。
return null;
}
}
通过上面的分析,可以清晰的明白,在对同一重入缓存的处理方法,读写锁还是比单独的双重synchronized要优越那么一点。主要表现在,读锁可以对读锁代码块中进行并发访问而对写锁是排斥的。 写锁呢是对所有的读锁或写锁排斥的,它是独占式的排斥。这个在某此应用下可谓好处多多啊。
比如下面的应用:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* BankCard类,为信用卡类。有卡号和初始的金额 1w元。
* Consumer类,为儿子类,只是去消费信用卡金额。
* Consumer2类,为父母类,只是去查看信用卡金额。
*
* 从生活应用中,应该的需求是当消费信用卡时,尚在消费中未消费完,肯定是不允许查看余额的。
* 而一旦在查看余额时开始,到查看余额结束之前都是不允许再进行消费的。
*
* 查看余额的多个线程,可以并发的查看余额。但消费时,只能独占式的消费,只有一个线程消费完了,另一个线程才能去消费或查看余额。
* 绝对不允许,有消费的同时,进行再次消费或查看余额的。因为这种情况很容易产生数据丢失或查看了一个错误的数据。
* @author chen
*
*/
public class ParentReadWriteLock {
public static void main(String[] args) {
BankCard bc = new BankCard();
ReadWriteLock lock = new ReentrantReadWriteLock();
ExecutorService pool = Executors.newCachedThreadPool();
Consumer cm1 = new Consumer(bc, lock);
Consumer2 cm2 = new Consumer2(bc, lock , 1);
Consumer2 cm3 = new Consumer2(bc, lock , 2);
pool.execute(cm2);
pool.execute(cm3);
pool.execute(cm1);
}
}
class BankCard {
private String cardid = "XZ456789";
private int balance = 10000;
public String getCardid() {
return cardid;
}
public void setCardid(String cardid) {
this.cardid = cardid;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
}
/**
* @说明 儿子类,只消费
*/
class Consumer implements Runnable {
BankCard bc = null;
ReadWriteLock lock = null;
Consumer(BankCard bc, ReadWriteLock lock) {
this.bc = bc;
this.lock = lock;
}
public void run() {
try {
while(true){ //也就是当获取到写锁要去写时,读锁则获取不到,不允许执行读锁套起来的代码
lock.writeLock().lock();
System.out.print("儿子要消费,现在余额:" + bc.getBalance() + "\t");
bc.setBalance(bc.getBalance() - 2000);
//Thread.sleep(5 * 1000); //即使在这里释放了CPU执行权,但下面的读取依旧没有权限。因为加了读锁
System.out.println("儿子消费2000元,现在余额:" + bc.getBalance());
lock.writeLock().unlock();
Thread.sleep(2 * 1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* @说明 父母类,只监督
*/
class Consumer2 implements Runnable {
BankCard bc = null;
int type = 0;
ReadWriteLock lock = null;
Consumer2(BankCard bc, ReadWriteLock lock,int type) {
this.bc = bc;
this.lock = lock;
this.type = type;
}
public void run() {
try {
while(true){
lock.readLock().lock();
if(type==2){
System.out.println("父亲准备要去查余额了呢++");
Thread.sleep(5 * 1000); //当读锁获取到了执行权执行上面语句后即使这里睡了5秒,写锁也不具备执行权,依然要等读锁执行完了。再抢执行权
System.out.println("父亲要查询,现在余额:" + bc.getBalance());
}
else{
System.out.println("老妈准备要去查余额了呢----"); //这里可以测试出读锁是可以支持多线程并发操作的
//可以执行看出,老爸进来查余额时,老妈也可以进来查余额。
Thread.sleep(5 * 1000);
System.out.println("老妈要查询,现在余额:" + bc.getBalance());
}
lock.readLock().unlock(); //若将这里的unlock注释掉,即注释读锁的释放,则以后写锁代码块则永不能执行。
//因为没有释放读锁,对JVM意味依然还在读,写是不能进入的。同理注释上面的写的unlock,读也不能再执行了。
Thread.sleep(1 * 1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
至于 Lock与synchronized的区别及应用场景,请参见上遍http://blog.csdn.net/chenshufei2/article/details/7894992这里不再重复了。
总结一下读写锁:
1..重入方面其内部的WriteLock可以获取ReadLock,但是反过来ReadLock想要获得WriteLock则永远都不要想.
2.WriteLock可以降级为ReadLock,顺序是:先获得WriteLock再获得ReadLock,然后释放WriteLock,这时候线程将保持Readlock的持有.反过来ReadLock想要升级为WriteLock则不可能,为什么?参看1,呵呵.
3.ReadLock可以被多个线程持有并且在作用时排斥任何的WriteLock,而WriteLock则是完全的互斥.这一特性最为重要,因为对于高读取频率而相对较低写入的数据结构,使用此类锁同步机制则可以提高并发量.你可以把ReadLock想成狼,而WriteLock是狮子。狼是群体的,分肉是可以的。狮子一般都是独居的,不给分肉不给同居的。哈哈。但狼群们是排拆狮子的。不让会狮子一起参与分肉呢。
4.不管是ReadLock还是WriteLock都支持Interrupt,语义与ReentrantLock一致.
5.WriteLock支持Condition并且与ReentrantLock语义一致,而ReadLock则不能使用Condition,否则抛出UnsupportedOperationException异常.
读写锁应用的另一个场景可以为一个集合加上读写锁,就假设是Map吧,所有线程可以进来查数据,即get(String key)或 String[] allKeys()。而查数据时,不允许再put 进数据,要加锁,加个读锁。可以并发的查看数据呢。
而 在put(key,value)时,则是排它共享的,put的同时既不允许进行读更也不允许有put,所以要加个写锁。
分享到:
相关推荐
在linux下按照windows的slim read/write lock算法实现的读写锁源码。
Excel2007ReadWrite.rar读写Excel 读写Excel Excel 读写文件Excel2007ReadWrite.rar读写Excel 读写Excel Excel 读写文件
用Mutex和Semaphore实现了Windows下的读写锁。开发环境为Visual Stdio 2008.
驱动读写进程内存,能够读取高地址的所有字节,能够写入高地址的所有字节
XML ReadWrite经典实例,有注释. 用C#实现XML文件读写的示例,比较详尽,简单易懂,认真看两三分钟即可掌握如何创建一个较复杂的XML文档 供有需要的同学学习参考.
读写磁盘的实现原理,非常不错,是用VC实现的。
PDF Read Write
Python 提供的多线程模型中并没有提供读写锁,读写锁相对于单纯的互斥锁,适用性更高,可以多个线程同时占用读模式的读写锁,但是只能一个线程占用写模式的读写锁。 通俗点说就是当没有写锁时,就可以加读锁且任意...
Python 提供的多线程模型中并没有提供读写锁,读写锁相对于单纯的互斥锁,适用性更高,可以多个线程同时占用读模式的读写锁,但是只能一个线程占用写模式的读写锁。 通俗点说就是当没有写锁时,就可以加读锁且任意...
RW-Read & Write Utility是一个功能很强大的硬件读写工具,基本上电脑上的各种系统设备都可以查看。 可在Windows下读取PCI、SMBIOS、IO、Memory、Bios及其他硬件信息查看,如Slic版本。 强大的实用程序,适用于硬件...
Read and Write Utility
ds2431 read write software
ShapeFile ReadWrite OCX & DLL 读写控件
很不错的小工具,能完成几乎所有硬件读写
WDM Read Write METHOD
Mifare read write demo
Windows 8: WinRT中文件的正确读写方法
Matlab MP3readwriteM文件代码,使用方式与wavread等类似,可以支持目前MP3等格式音频文件。
《HotR: Alleviating Read/Write Interference with Hot Read Data Replication for Flash Storage》论文的原文及中文翻译。 摘要: 在读写请求混合的工作负载下,闪存的读写干扰问题仍是一个关键问题。为了显著...
简单高效的跨进程锁,支持读写锁分离, 也可以用于文件锁Getting startedIn your build.gradle:allprojects { repositories { maven { url 'https://jitpack.io' } } } dependencies { ...