线程高级---读写锁
2008-08-05 14:30
169 查看
读写锁问题也是比较常见的问题,这是因为现实中充斥着这类问题,而读者写者问题是单纯synchronized的进化版,之所以这么说,是因为它将锁分为读锁和写锁,通过读锁之间的并发性和写锁的排他性从而极大的提升性能。
在开始之前,还是先来回顾一下读写锁问题。一个数据文件或记录能被多个线程共享。有些线
程要求读,而另一些则要求写或修改。允许多个读线程同时读一个共享对象,因为读操作不会使数据文件混乱,但绝不允许一个写线程和其他读或写线程同时访问共
享对象。因为这种访问违反了Bernstein条件。
根据上面的描述,我们可以看出,读锁跟一般的互斥有些不一样,它只互斥写锁,但却不管读锁,换句话说,读锁之间是可以大量并发执行的。这样的例子随处可
见,比如,机票售票系统,在买票高峰时期常常都是多个售票窗口同时查询,而不会发生多个人订了同一个座位或者说座位空着却不能订的情况。那么,读写锁在之
间是如何发挥其并发的优越性的。当多个售票窗口同时查询时,由于是读取操作,可以同时执行,就是说大家都能看,而不是要排队一个一个看。而一旦有一个人订
了票,则在读锁完全解除时,这个写锁就会将票mark为已经购买。之所以不会发生多人同订了一个座位,是因为读锁具有排他性,一旦有个读锁在修改,则不能
有其它读锁同时修改。类似的例子还有很多,比如证卷交易等等(注意这里举的两个例子并非是线程的读写锁,通常情况下是利用了关系数据库的读写锁来提升并发
性的,这里为了更通俗,从而举这两个常见的例子)
下图是整个读写锁的框架,对这个框架有个概念性的认识,在复用时需要修改的是哪些?
下面是一个应用读写锁的简单例子。每段程序会加以解读。
这里设置了4个变量readingReader,writingWriter,waitingWriter,tendWrite,分别代表正在读的读者,
正在写的写者,等待中的写者以及一个用来评估读还是写的判断条件。这个评估条件是非常有用的,它能够保证程序不会只运行读线程(因为读的量较大),而是适
当的让出一些机会给写线程。读锁定中while(writingWriter
> 0 || (tendWrite && waitingWriter > 0))
就是存在正在写的线程(意味着现在的机会在写线程手中)或者评估为写并且正在等待的写线程超过一个(意味着之后的机会应该给写线程)那就得等待。而写锁的
等待条件则是while(readingReader
> 0 || writingWriter > 0)
意味着有正在运行的读或写线程,则必须等待,这体现了写的排他性。而这里waitingWriter++;后面又
finally{waitingWriter--;}可能觉得很奇怪,怎么加一会减一会的,其实这里用到了线程设计的before/after
pattern,只有当进入wait而没有发生任何错我们才知道它在等待,可惜我们没办法在wait里设置,于是before
wait,我们就用了waitingWriter++;表示已经开始等待了,而无论是被中断取消或是正常唤醒,我们都要waitingWriter--;
表示结束。解锁就不在解释了,注意观察评估变量的设定及其作用。
这个类是实际的资源类,主要就是read和write方法,read负责读出所有对象并将队列清空,而write负责写入对象。注意读写锁的使用。
注意这里是通过两个逻辑上实现的lockReader和lockWriter来锁定程序,跟synchronized不同的是,这两个锁实际上用到了synchronized,只是比它多了些判读条件。
Reader类。就是将read读入的内容显示出来。
Writer。负责写入一个request
主测试类。主要负责调用Reader和Writer
最后,值得提醒的是,类似这样的问题都能抽象出一个共同的特征:那就是读线程比较多或者读操作比较繁重。如果大多数线程是写操作的话,就得评估衡量用读写锁来解决是否合算了。
在开始之前,还是先来回顾一下读写锁问题。一个数据文件或记录能被多个线程共享。有些线
程要求读,而另一些则要求写或修改。允许多个读线程同时读一个共享对象,因为读操作不会使数据文件混乱,但绝不允许一个写线程和其他读或写线程同时访问共
享对象。因为这种访问违反了Bernstein条件。
根据上面的描述,我们可以看出,读锁跟一般的互斥有些不一样,它只互斥写锁,但却不管读锁,换句话说,读锁之间是可以大量并发执行的。这样的例子随处可
见,比如,机票售票系统,在买票高峰时期常常都是多个售票窗口同时查询,而不会发生多个人订了同一个座位或者说座位空着却不能订的情况。那么,读写锁在之
间是如何发挥其并发的优越性的。当多个售票窗口同时查询时,由于是读取操作,可以同时执行,就是说大家都能看,而不是要排队一个一个看。而一旦有一个人订
了票,则在读锁完全解除时,这个写锁就会将票mark为已经购买。之所以不会发生多人同订了一个座位,是因为读锁具有排他性,一旦有个读锁在修改,则不能
有其它读锁同时修改。类似的例子还有很多,比如证卷交易等等(注意这里举的两个例子并非是线程的读写锁,通常情况下是利用了关系数据库的读写锁来提升并发
性的,这里为了更通俗,从而举这两个常见的例子)
下图是整个读写锁的框架,对这个框架有个概念性的认识,在复用时需要修改的是哪些?
下面是一个应用读写锁的简单例子。每段程序会加以解读。
/** * 2007-6-19 * Queue.java * package ReadWriteLock * TODO As a lock * levi */ package ReadWriteLock; public class ReadWriteLock { private int readingReader; private int writingWriter; //0 or 1 private int waitingWriter; private boolean tendWrite; public synchronized void lockReader(){ while(writingWriter > 0 || (tendWrite && waitingWriter > 0)){ try{ wait(); } catch(InterruptedException ie){ } } readingReader++; } public synchronized void lockWriter() throws InterruptedException{ waitingWriter++; try{ while(readingReader > 0 || writingWriter > 0){ wait(); } } finally{ waitingWriter--; } writingWriter++; } public synchronized void unlockReader(){ readingReader--; tendWrite = true; notifyAll(); } public synchronized void unlockWriter(){ writingWriter--; tendWrite = false; notifyAll(); } } |
正在写的写者,等待中的写者以及一个用来评估读还是写的判断条件。这个评估条件是非常有用的,它能够保证程序不会只运行读线程(因为读的量较大),而是适
当的让出一些机会给写线程。读锁定中while(writingWriter
> 0 || (tendWrite && waitingWriter > 0))
就是存在正在写的线程(意味着现在的机会在写线程手中)或者评估为写并且正在等待的写线程超过一个(意味着之后的机会应该给写线程)那就得等待。而写锁的
等待条件则是while(readingReader
> 0 || writingWriter > 0)
意味着有正在运行的读或写线程,则必须等待,这体现了写的排他性。而这里waitingWriter++;后面又
finally{waitingWriter--;}可能觉得很奇怪,怎么加一会减一会的,其实这里用到了线程设计的before/after
pattern,只有当进入wait而没有发生任何错我们才知道它在等待,可惜我们没办法在wait里设置,于是before
wait,我们就用了waitingWriter++;表示已经开始等待了,而无论是被中断取消或是正常唤醒,我们都要waitingWriter--;
表示结束。解锁就不在解释了,注意观察评估变量的设定及其作用。
/** * 2007-6-19 * Queue.java * package ReadWriteLock * TODO As a shared concurrent resource * levi */ package ReadWriteLock; import java.util.*; /** * @author levi * */ public class Queue { private LinkedList queue = new LinkedList(); private ReadWriteLock rwLock = new ReadWriteLock(); public Queue(String str){ StringTokenizer st = new StringTokenizer(str.toString()); while(st.hasMoreTokens()) queue.add(st.nextToken()); } public Object read() throws InterruptedException{ rwLock.lockReader(); try{ String s = ""; for(int i = 0;i < queue.size();i++){ s += queue.remove(0); } return s; } finally{ rwLock.unlockReader(); } } public void write(Object obj) throws InterruptedException{ rwLock.lockWriter(); try{ StringTokenizer st = new StringTokenizer(obj.toString()); while(st.hasMoreTokens()) queue.add(st.nextToken()); } finally{ rwLock.unlockWriter(); } } } |
注意这里是通过两个逻辑上实现的lockReader和lockWriter来锁定程序,跟synchronized不同的是,这两个锁实际上用到了synchronized,只是比它多了些判读条件。
/** * 2007-6-19 * Reader.java * package ReadWriteLock * TODO As a reader to read data concurrently from queue * levi */ package ReadWriteLock; public class Reader extends Thread{ private Queue queue; public Reader(Queue queue){ this.queue = queue; } public void run(){ try{ System.out.println(Thread.currentThread().getName() + " Query: " + queue.read().toString()); } catch(InterruptedException ie){ } } } |
/** * 2007-6-19 * Writer.java * package ReadWriteLock * TODO As a writer to write data to queue * levi */ package ReadWriteLock; public class Writer extends Thread{ private Queue queue; private String request; public Writer(Queue queue,String request){ this.queue = queue; this.request = request; } public void run(){ try{ queue.write(request); } catch(InterruptedException ie){ } } } |
/** * 2007-6-19 * Test.java * package ReadWriteLock * TODO the main thread * levi */ package ReadWriteLock; /** * @author levi * */ public class Test { public static void main(String [] args){ Queue q = new Queue("* * * * * * * * * *"); new Writer(q,"I find that I had fallen into you, beautiful girl").start(); new Writer(q,"But also I am wasting my time,flower becomes air").start(); new Reader(q).start(); new Reader(q).start(); new Reader(q).start(); new Reader(q).start(); new Reader(q).start(); } } |
最后,值得提醒的是,类似这样的问题都能抽象出一个共同的特征:那就是读线程比较多或者读操作比较繁重。如果大多数线程是写操作的话,就得评估衡量用读写锁来解决是否合算了。
相关文章推荐
- 线程高级篇-读写锁ReentrantReadWriteLock
- Java高级部分--线程重点总结
- 线程高级篇-Lock锁和Condition条件
- 多线程:线程状态、synchronized关键字、读写锁、条件对象、Volatile、阻塞队列等小结
- UNIX环境高级编程学习之第十一章线程-使用读写锁
- Java的一些高级特性(八)——Java7中的线程
- ReadWriteLock读写锁实现线程读写互斥问题
- 高级Linux程序设计第四章:线程
- UNIX环境高级编程--第十一章线程总结
- vc++高级班之多线程篇[7]---线程间的同步机制②
- UI高级------多线程(线程与进程)
- 秒杀多线程第十六篇 多线程十大经典案例之一 双线程读写队列数据
- Java高级--Java线程运行栈信息的获取 getStackTrace()
- 关于多个线程读写文件
- UNIX环境高级编程笔记之线程
- APP 缓存数据线程安全问题,多个线程同时对同一资源进行读写问题
- UNIX环境高级编程 第12章 线程控制
- [Unix环境高级编程] 线程
- 10.C#高级编程--进程与线程
- 线程使用之读写锁