`
lxy2330
  • 浏览: 460159 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

线程高级---读写锁

 
阅读更多
读写锁问题也是比较常见的问题,这是因为现实中充斥着这类问题,而读者写者问题是单纯synchronized的进化版,之所以这么说,是因为它将锁分为读锁和写锁,通过读锁之间的并发性和写锁的排他性从而极大的提升性能。
    在开始之前,还是先来回顾一下读写锁问题。一个数据文件或记录能被多个线程共享。有些线程要求读,而另一些则要求写或修改。允许多个读线程同时读一个共享对象,因为读操作不会使数据文件混乱,但绝不允许一个写线程和其他读或写线程同时访问共享对象。因为这种访问违反了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();
 }
}

     这里设置了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--; 表示结束。解锁就不在解释了,注意观察评估变量的设定及其作用。

/**
 * 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();
  }
 }
}

     这个类是实际的资源类,主要就是read和write方法,read负责读出所有对象并将队列清空,而write负责写入对象。注意读写锁的使用。

     注意这里是通过两个逻辑上实现的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){
   
  }
 }
}

     Reader类。就是将read读入的内容显示出来。

 

/**
 * 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){
   
  }
 }
}

     Writer。负责写入一个request

 

/**
 * 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();
  
 }
}

    主测试类。主要负责调用Reader和Writer

 

    最后,值得提醒的是,类似这样的问题都能抽象出一个共同的特征:那就是读线程比较多或者读操作比较繁重。如果大多数线程是写操作的话,就得评估衡量用读写锁来解决是否合算了。

分享到:
评论

相关推荐

    易语言线程安全之原子锁与读写锁-易语言

    多线程操作中保证线程安全的两个锁,原子锁只写了一个整数的;读写锁是 Windows 提供的一个精简版的读写锁,一般情况下也够用了。 使用说明和注意事项都在源码里,大家自己看吧,截图:

    尚硅谷Java视频_JUC 视频教程

    尚硅谷_JUC线程高级_ReadWriteLock 读写锁 ·12. 尚硅谷_JUC线程高级_线程八锁 ·13. 尚硅谷_JUC线程高级_线程池 ·14. 尚硅谷_JUC线程高级_线程调度 ·15. ForkJoinPool 分支合并框架-工作窃取

    易语言程序免安装版下载

    数据库操作支持库增加读写长整数字段的功能,但受限于系统接口暂不能读写超出整数范围的数值。 7. 修改高级表格支持库,通过鼠标调整行高列宽时不改变当前光标行列号。 8. 修改BUG:在IDE中打开源代码文件(.e)后...

    LINUX高级程序设计(中文第二版)杨宗德 (1)

    详细介绍了linux系统下编程环境及编程工具、文件管理(文件类型、ansi以及posix标准下文件读写操作)、进程管理(创建、退出、执行、等待、属性...线程间同步(互斥锁、读写锁、条件变量)以及网络基本编程、高级应用等内容...

    LINUX高级程序设计(中文第二版)杨宗德 (2)end

    详细介绍了linux系统下编程环境及编程工具、文件管理(文件类型、ansi以及posix标准下文件读写操作)、进程管理(创建、退出、执行、等待、属性...线程间同步(互斥锁、读写锁、条件变量)以及网络基本编程、高级应用等内容...

    Linux高级程序设计 (不完整版只到11.5.2章节)

    详细介绍了Linux系统下编程环境及编程工具、文件管理(文件类型、ANSI以及POSIX标准下文件读写操作)、进程管理(创建、退出、执行、等待、...线程间同步(互斥锁、读写锁、条件变量)以及网络基本编程、高级应用等内容...

    滴水逆向培训高级班

    │ 013 跨进程读写内存.mp4, E0 ^0 U I1 h │ $ {. ?; O) C* w1 K. q) K └─驱动开发 01 驱动开发环境配置.mp4* W) g2 z& T/ _; @1 V" n 02 第一个驱动程序.mp4 03 如何调试驱动程序.mp4; [8 U2 T) B' V 04 ...

    java集合-ConcurrentHashMap的使用

    ConcurrentHashMap使用了分段锁(Segment)来实现并发的读写操作,每个Segment都相当于一个小的HashMap,将整个哈希表分成多个部分。这样可以同时进行多个线程的并发读写操作,不会阻塞其他线程的访问。 需要注意的...

    1000个【易语言模块大全汇总批量下载】

    2005-10-21 15:30 14164 3949 易语言模块大全\IC卡读写模块 1.0.ec 2002-06-24 10:54 5555 2002 易语言模块大全\IP地址编辑框2.0.ec 2002-03-26 10:00 1765 740 易语言模块大全\kernel模块.EC 2005-10-21 15:30 ...

    易语言模块大全汇总批量下载

    2005-10-21 15:30 14164 3949 易语言模块大全\IC卡读写模块 1.0.ec 2002-06-24 10:54 5555 2002 易语言模块大全\IP地址编辑框2.0.ec 2002-03-26 10:00 1765 740 易语言模块大全\kernel模块.EC 2005-10-21 15:30 ...

    E语言1000模块

    2005-10-21 15:30 14164 3949 易语言模块大全\IC卡读写模块 1.0.ec 2002-06-24 10:54 5555 2002 易语言模块大全\IP地址编辑框2.0.ec 2002-03-26 10:00 1765 740 易语言模块大全\kernel模块.EC 2005-10-21 15:30 ...

    UNIX 高级教程系统技术内幕

    7.8 读写锁 7.8.1 设计考虑 7.8.2 实现 7.9 引用计数 7.10 其他考虑 7.10.1 死锁避免 7.10.2 递归锁 7.10.3 阻塞还是自旋 7.10.4 锁什么 7.10.5 粒度和持续时间 7.11 例子分析 7.11.1 SVR 4.2/MP 7.11.2 Digital ...

    《深入理解 Rust 并发编程》

    同步原语:各种同步原语进行了深入分析,如互斥锁(Mutex)、读写锁(RwLock)、条件变量(Condvar)、屏障(Barrier)等。 并发集合:如何安全地在多线程环境中使用集合类型,如 Vec、HashMap

    Mysql高级性能优化思维导图

    MySQL高级性能优化的关键点: 数据库设计和规范化:确保数据库表结构合理、字段类型正确,并进行适当的规范化,以减少数据冗余和提高查询效率。 索引优化:根据查询需求创建适当的索引,避免过多或不必要的索引,...

    java面试笔试资料包括JAVA基础核心知识点深度学习Spring面试题等资料合集.zip

    线程锁之重入锁.docx 线程间的通信.docx 虚拟机内存结构和垃圾回收docx.docx 锁分类的了解.docx 集合的扩容机制.png SpringMVC部分.docx Spring部分.docx 第一题.pdf 第七题 谈谈MySQL支持的事务隔离级别 (1).pdf 第...

    vc++ 开发实例源码包

    内含各种例子(vc下各种控件的使用方法、标题栏与菜单栏、工具栏与状态栏、图标与光标、程序窗口、程序控制、进程与线程、字符串、文件读写操作、文件与文件夹属性操作、文件与文件夹系统操作、系统控制操作、程序...

    python的教程.txt

    Python教程可以从基础入门开始,逐步深入到高级编程和实际... 学习Python的多线程和多进程编程,了解GIL(全局解释器锁)的概念和影响。 三、Python高级特性 掌握Python的装饰器、生成器、迭代器、闭包等高级特性。

    易语言模块914个

    ACCESS到高级表格.ec Access操作.ec ACCSEE记录显示到超级列表框模块.ec ACSII加解密模块2.0.ec AD.ec ADO方式填充树型框.ec API操作模块.ec Base64编解码.ec BASE64编解码模块.ec Bios信息.ec BMP滤镜...

    linux programming instances网络编程教程 附源代码

    9.3.2 读写锁的操作函数 9.4 记录上锁 9.4.1 记录上锁的基本原理 9.4.2 fcntl记录上锁 9.4.3 记录上锁应用举例 9.5 本章小结 第10章 多路复用和信号驱动i/0 10.1 多路复用 10.1.1 多路复用的基本原理...

Global site tag (gtag.js) - Google Analytics