信号量机制实现读者写者问题(思路剖析+Java代码实现+验证)

article/2025/9/30 7:56:57

写在前面:

Java中:

  1. 我们用这样的代码新建一个信号量:Semaphore mutex = new Semaphore(1);
  2. P操作(wait)的代码为:mutex.acquire();
  3. V操作(signal)的代码为:mutex.release();

本文章的内容:

  1. 读者写者问题(读者优先)
  2. 读者写者问题(公平)
  3. 读者写者问题(写者优先)

 
及其:

  1. 思路
  2. 验证

 
 
 
 

读者优先

思路:
读者写者问题大概是:对于临界文件的访问,不允许写-写,不允许写-读,但允许读-读
核心难点在于:读者和读者之间是不互斥的——那么,单单使用一个wr读写互斥锁,是不足以满足需求的
我们的解决方案是:既然读者之间不互斥,我们可以让只有第一个进入的读者需要P(rw),只有最后一个退出的读者需要V(rw)
即:第二、第三…之后的读者都跳过了P(rw),从而不被阻塞!
 
考虑下面一种情况:
Writer一个不小心没抢过Reader,rw锁被Reader拿走。之后,如果Reader源源不断的到来,源源不断的跳过P(rw),源源不断的使得readerCnt++…
那么,直到最后一个Reader退出释放rw,所有的Writer将永远因为得不到rw而阻塞,即发生饥饿——体会一下,这是不是读者优先?

SharedData.java

public class SharedData {public Semaphore rw = new Semaphore(1);			// 读写锁(本质是个互斥锁)public Semaphore mutex = new Semaphore(1);		// 读者计数器の互斥锁public int readerCnt = 0;						// 读者计数器
}

Writer.java

public class Writer implements Runnable {SharedData sharedData;public Writer(SharedData sharedData){this.sharedData = sharedData;}private void write(){System.out.println(Thread.currentThread().getName() + " Writing...");}@Overridepublic void run() {while (true){try {// 对于写者,直接用「读写锁」锁一下就行了,读者稍稍会麻烦一些 >_<sharedData.rw.acquire();write();sharedData.rw.release();// // 睡一小会儿,不然while死循环刷得太快了Thread.sleep(new Random().nextInt(2));} catch (InterruptedException e) {e.printStackTrace();}}}
}

Reader.java

public class Reader implements Runnable {SharedData sharedData;public Reader(SharedData sharedData){this.sharedData = sharedData;}private void read(){System.out.println(Thread.currentThread().getName() + " Reading...");}@Overridepublic void run() {while (true){try {// 第一个(cnt==0)读者进行P操作sharedData.mutex.acquire();if(sharedData.readerCnt == 0){sharedData.rw.acquire();}sharedData.readerCnt++;sharedData.mutex.release();// 开始读 >_<read();// 最后一个(cnt==0)读者进行V操作sharedData.mutex.acquire();sharedData.readerCnt--;if(sharedData.readerCnt == 0){sharedData.rw.release();}sharedData.mutex.release();// 睡一小会儿,不然while死循环刷得太快了Thread.sleep(new Random().nextInt(2));} catch (InterruptedException e) {e.printStackTrace();}}}
}

Test.java

public class Test {public static void main(String[] args) {// 共享数据(信号量计数器,啥的...)SharedData sharedData = new SharedData();// 1写者new Thread(new Writer(sharedData)).start();// 9读者new Thread(new Reader(sharedData)).start();new Thread(new Reader(sharedData)).start();new Thread(new Reader(sharedData)).start();new Thread(new Reader(sharedData)).start();new Thread(new Reader(sharedData)).start();new Thread(new Reader(sharedData)).start();new Thread(new Reader(sharedData)).start();new Thread(new Reader(sharedData)).start();new Thread(new Reader(sharedData)).start();}
}

验证:
上面分析过,Reader源源不断地到来会导致Writer的饥饿
那么,我们安排1:9的写/读者比例,模拟这种场景——你会发现,Writer很难得到一次运行(下图)
再取极端的情况,删掉Reader的Thread.sleep,让读者真正“源源不断”——Writer将完全饥饿(自行试一下吧!)

在这里插入图片描述

 
 
 
 

公平读写

思路:
上面的「读者优先」引入了一种思想:通过计数来实现跳过P(rw)——这非常巧妙
这里,我们再引入一种极为巧妙的思想:设置一个queue信号信号量,来实现一个等待队列
 
如何实现所谓“等待队列”的呢?不妨用一个例子,通俗地顺着走一下:(读 - 写 - 读)

  • 未使用queue信号量:Reader到来拿走rw;Writer到来被阻塞;第二个Reader到来,因为readerCnt != 0而跳过P(rw)开始执行——所谓“读者优先”
  • 使用queue信号量:Reader到来拿走queue,接着拿走rw后归还queue;Writer到来拿走queue,接着被P(rw)阻塞;第二个Reader到来,上来就被P(queue)阻塞,它何时才能执行呢?必须等到第一个Reader归还rw,Writer得到rw执行完毕后,归还queue,第二个Reader才得以执行——queue使第二个Reader强制等待,形成一个“Reader-Writer-第二个Reader”的固定执行顺序——所谓“等待队列”

SharedData.java

public class SharedData {public Semaphore rw = new Semaphore(1);			// 读写锁(本质是个互斥锁)public Semaphore mutex = new Semaphore(1);		// 读者计数器の互斥锁public Semaphore queue = new Semaphore(1);		// 队列锁public int readerCnt = 0;						// 读者计数器
}

Writer.java

public class Writer implements Runnable {SharedData sharedData;public Writer(SharedData sharedData){this.sharedData = sharedData;}private void write(){System.out.println(Thread.currentThread().getName() + " Writing...");}@Overridepublic void run() {while (true){try {// 核心逻辑sharedData.queue.acquire();sharedData.rw.acquire();write();sharedData.rw.release();sharedData.queue.release();// 睡一觉zzzThread.sleep(new Random().nextInt(2));} catch (InterruptedException e) {e.printStackTrace();}}}
}

Reader.java

public class Reader implements Runnable {SharedData sharedData;public Reader(SharedData sharedData){this.sharedData = sharedData;}private void read(){System.out.println(Thread.currentThread().getName() + " Reading...");}@Overridepublic void run() {while (true){try {// 第一个(cnt==0)读者进行P操作sharedData.queue.acquire();sharedData.mutex.acquire();if(sharedData.readerCnt == 0){sharedData.rw.acquire();}sharedData.readerCnt++;sharedData.mutex.release();sharedData.queue.release();// 开始读 >_<read();// 最后一个(cnt==0)读者进行V操作sharedData.mutex.acquire();sharedData.readerCnt--;if(sharedData.readerCnt == 0){sharedData.rw.release();}sharedData.mutex.release();// 睡一觉zzzThread.sleep(new Random().nextInt(2));} catch (InterruptedException e) {e.printStackTrace();}}}
}

Test.java

public class Test {public static void main(String[] args) {// 共享数据(信号量,计数器啥的...)SharedData sharedData = new SharedData();// 5写者new Thread(new Writer(sharedData)).start();new Thread(new Writer(sharedData)).start();new Thread(new Writer(sharedData)).start();new Thread(new Writer(sharedData)).start();// 5读者new Thread(new Reader(sharedData)).start();new Thread(new Reader(sharedData)).start();new Thread(new Reader(sharedData)).start();new Thread(new Reader(sharedData)).start();}
}

验证:
既然已经实现了读写公平,那么我们便安排5:5的写/读者比例即可
很直观地可以看出,读者写者得到了“公平”的待遇(下图)

在这里插入图片描述

 
 
 
 

写者优先

思路:
这是读者优先所用思想的再一次运用。为什么Reader得以优先呢?

  • 因为只有第一个Reader会被rw阻塞,之后来到的Reader可以跳过P(rw);而所有的Writer都会被rw阻塞——这就是Reader得到的“特权”
     

仿写句子:如何让Writer得以优先呢?

  • 我们让只有第一个Writer会被queue阻塞,之后来到的Writer可以跳过P(queue);而所有的Reader都会被queue阻塞——这就是Writer得到的“特权”

SharedData.java

public class SharedData {public Semaphore rw = new Semaphore(1);			// 读写锁(本质是个互斥锁)public Semaphore mutex = new Semaphore(1);		// 读者计数器の互斥锁public Semaphore mutex2 = new Semaphore(1);		// 写者计数器の互斥锁public Semaphore queue = new Semaphore(1);		// 队列锁public int readerCnt = 0;						// 读者计数器public int writerCnt = 0;						// 写者计数器
}

Writer.java

public class Writer implements Runnable {SharedData sharedData;public Writer(SharedData sharedData){this.sharedData = sharedData;}private void write(){System.out.println(Thread.currentThread().getName() + " Writing...");}@Overridepublic void run() {while (true){try {// 只有第一个进入的写者才需要排队sharedData.mutex2.acquire();if(sharedData.writerCnt == 0){sharedData.queue.acquire();}sharedData.writerCnt++;sharedData.mutex2.release();sharedData.rw.acquire();write();sharedData.rw.release();// 最后一个退出的写者释放queuesharedData.mutex2.acquire();sharedData.writerCnt--;if(sharedData.writerCnt == 0){sharedData.queue.release();}sharedData.mutex2.release();// 睡一觉zzzThread.sleep(new Random().nextInt(2));} catch (InterruptedException e) {e.printStackTrace();}}}
}

Reader.java

public class Reader implements Runnable {SharedData sharedData;public Reader(SharedData sharedData){this.sharedData = sharedData;}private void read(){System.out.println(Thread.currentThread().getName() + " Reading...");}@Overridepublic void run() {while (true){try {// 所有的读者会被queue阻塞sharedData.queue.acquire();sharedData.mutex.acquire();if(sharedData.readerCnt == 0){sharedData.rw.acquire();}sharedData.readerCnt++;sharedData.mutex.release();sharedData.queue.release();// 开始读 >_<read();sharedData.mutex.acquire();sharedData.readerCnt--;if(sharedData.readerCnt == 0){sharedData.rw.release();}sharedData.mutex.release();// 睡一觉zzzThread.sleep(new Random().nextInt(2));} catch (InterruptedException e) {e.printStackTrace();}}}
}

Test.java

public class Test {public static void main(String[] args) {// 共享数据(信号量,计数器啥的...)SharedData sharedData = new SharedData();// 1读者new Thread(new Reader(sharedData)).start();// 9写者new Thread(new Writer(sharedData)).start();new Thread(new Writer(sharedData)).start();new Thread(new Writer(sharedData)).start();new Thread(new Writer(sharedData)).start();new Thread(new Writer(sharedData)).start();new Thread(new Writer(sharedData)).start();new Thread(new Writer(sharedData)).start();new Thread(new Writer(sharedData)).start();new Thread(new Writer(sharedData)).start();}
}

验证:
和「读者优先」一样的思路。我们设置1:9的读/写比例,模拟“源源不断”的Writer——Reader难以得到一次运行(下图)
再取极端的情况,删掉Writer的Thread.sleep,让读者真正“源源不断”——Reader将完全饥饿(自行试一下吧!)

在这里插入图片描述

 
 
 
 
 
 
 
 

上面为了能直观展示Writer/Reader的执行情况,仅仅打印了一个“writing/reading”作为演示
如果想成为一个真正的读者/写者,可以将write()/read()方法中的代码替换成:

write()

String path = "/Users/你的自定义文件路径/共享文件.txt";try {// 从外部获取一行并写入文件(自动换行)BufferedWriter bw = new BufferedWriter(new FileWriter(new File(path), true));BufferedReader br = new BufferedReader(new InputStreamReader(System.in));bw.write(br.readLine());bw.write("\n");br.close();bw.close();
} catch (IOException e) {e.printStackTrace();
}

read()

String path = "/Users/你的自定义文件路径/共享文件.txt";try {// 从文件读取一行并打印出来(如果文本为空则打印null)BufferedReader br = new BufferedReader(new FileReader(new File(path)));String line = br.readLine();System.out.println(line);br.close();
} catch (FileNotFoundException e) {e.printStackTrace();
} catch (IOException e){e.printStackTrace();
}

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
E N D END END


http://chatgpt.dhexx.cn/article/mW7V8fZ9.shtml

相关文章

Java实现读者写者问题--读者优先

作者&#xff1a;凌杰林 简介 临界资源&#xff1a;同一时间只能由一个进程访问的资源 临界区&#xff1a;访问临界资源的代码段 读者写者问题&#xff1a;存在一个多个进程共享的数据区&#xff08;临界资源&#xff09;&#xff0c;该数据区可以是一个文件或者一块内存空间…

操作系统实验:读者写者问题

一&#xff0e;实验目的&#xff1a; 通过实现读者写者问题理解进程及信号量的概念 二&#xff0e;实验要求&#xff1a; 创建一个控制台进程&#xff0c;此进程包含n个线程。用这n个线程来表示n个读者或写者。每个线程按相应测试数据文件的要求进行读写操作。用信号量机制分…

信号量机制——读者-写者问题

信号量机制——读者-写者问题 问题描述 一个共享数据区&#xff0c;有若干个进程负责对其进行读入工作&#xff0c;若干个进程负责对其进行写入工作。 允许多个进程同时读数据互斥读数据若有进程写数据&#xff0c;不允许读者读数据 对照生活中的购票系统&#xff1a; 一个…

操作系统:读者写者问题

操作系统&#xff1a;读者写者问题 问题&#xff1a;一、读者-写者问题: 读者优先二、读者-写者问题&#xff1a;写进程优先 三、读者写者问题的应用---独木桥问题类型1.一次只能过一辆车类型2.一次能过多辆车 四、总结附代码&#xff1a;//读者优先//写者优先//公平竞争 教材原…

四、操作系统——读者写者问题(详解)

一、问题描述&#xff1a; 二、需要满足的条件&#xff1a; 写进程与写进程之间必须互斥的写入数据&#xff08;因为如果两个写进程同时对共享数据中的区域A中的数据进行写操作的话&#xff0c;会导致数据错误覆盖的问题&#xff09;写进程与读进程之间必须互斥的访问共享数据…

锁机制:读者写者问题 Linux C

最近碰到一些锁机制的问题&#xff0c;想起大三的时候做过的一个小课设&#xff0c;记录复习一下。 问题描述&#xff1a; 一个数据文件可以被多个进程共享&#xff0c;其中&#xff0c;有些进程要求读&#xff08;reader进程&#xff09;&#xff0c;而另一些进程要求对数据…

写优先的读者写者问题(Java实现)

该题系吉林大学19级软件学院操作系统课设题之一 先输入初始时的写者读者情况&#xff0c;优先级顺序做了随机处理 代码如下 GUI&#xff1a; import javax.swing.*; import javax.swing.border.Border; import javax.swing.text.BadLocationException; import javax.swing.tex…

操作系统读者写者问题代码实现

问题分析&#xff1a; 读者优先: 读者进程执行: 无其他读者写者, 直接执行有写者等, 但其他读者在读, 直接读有写者写, 等待写者进程执行: 无其他读写者, 直接执行有其他读写者, 等待 写者优先: 读者进程执行: 如果此时没有写者等待, 直接执行如果有写者等待, 那么等待写者…

读者写者问题详解 操作系统

2.16 读者写者问题 抽象解释 多个进程访问一个共享的数据区读者&#xff08;读进程&#xff09;只能读数据&#xff0c;写者&#xff08;写进程&#xff09;只能写数据适用于数据库、文件、内存、寄存器等数据区的访问模型如12306购票系统&#xff0c;由于用户量庞大和数据量…

同步机制—读者写者问题

【实验目的】 理解临界区和进程互斥的概念&#xff0c;掌握用信号量和PV操作实现进程互斥的方法。 【实验内容】 在windows或者linux环境下编写一个控制台应用程序&#xff0c;该程序运行时能创建N个线程&#xff0c;其中既有读者线程又有写者线程&#xff0c;它们按照事先设计…

操作系统——读者写者问题

一、问题描述 要求: 1、允许多个读者可以同时对文件执行读操作。 2、只允许一个写者往文件中写信息。 3、任一写者在完成写操作之前不允许其他读者或写者工作。 4、写者执行写操作前,应让已有的读者和写者全部退出。 二、问题分析 读者写者问题最核心的问题是如何处理…

【操作系统-进程】PV操作——读者写者问题

文章目录 读者写者问题万能模板万能模板 1——读进程优先万能模板 2——读写公平法万能模板 3——写进程优先 题目 1&#xff1a;南北过桥问题题目 2&#xff1a;录像厅问题题目 3&#xff1a;更衣问题 读者写者问题万能模板 读者写者问题&#xff0c;其本质就是连续多个同类进…

经典进程同步问题(三)——读者写者问题

目录 一、问题描述 二、解题思路 2.1 读者优先算法 2.2 写者优先算法 2.3 读写公平 三、源码实现 3.1 读者优先 3.2 写者优先 3.3 读写平等 一、问题描述 一个数据问价或记录可以被多个进程共享&#xff0c;我们把只读该文件的进程称为“读者进程”&#xff0c;其他进…

2. 堆与栈的区别

2. 堆与栈的区别 在理解这两个概念时&#xff0c;需要放到具体的场景下&#xff0c;因为不同场景下&#xff0c;堆与栈代表不同的含义。一般情况下&#xff0c;有两层含义&#xff1a; &#xff08;1&#xff09;程序内存布局场景下&#xff0c;堆与栈表示的是两种内存管理方式…

栈与堆的区别(内存分配与数据结构)

参考自https://blog.csdn.net/K346K346/article/details/80849966/ 堆&#xff08;Heap&#xff09;与栈&#xff08;Stack&#xff09;包含两层含义&#xff1a; 程序内存布局场景下的内存管理方式数据结构中的两种常见的数据结构 1. 程序内存分配中的堆与栈 1.1 栈介绍 …

C语言里栈和堆的区别整理

这里说的是C语言程序内存分配中的堆和栈。下面先谈谈C语言的内存管理&#xff1a; 可执行程序在存储时&#xff08;没有调到内存&#xff09;分为代码区&#xff08;text&#xff09;、数据区&#xff08;data&#xff09;和未初始化数据区&#xff08;bss&#xff09;3个部分。…

看懂堆与栈的区别与联系

<div class"simditor-body clearfix" style"height: auto; overflow: inherit;"><h2> <strong>  堆和栈概要</strong></h2>在计算机领域&#xff0c;堆栈是一个不容忽视的概念&#xff0c;堆栈是两种数据结构。堆栈都是一…

C语言:栈和堆的区别

c语言五大内存分区 栈区&#xff08;stack&#xff09;:存放函数形参和局部变量&#xff08;auto类型&#xff09;&#xff0c;由编译器自动分配和释放 堆区&#xff08;heap&#xff09;:该区由程序员申请后使用&#xff0c;需要手动释放否则会造成内存泄漏。如果程序员没有手…

什么是栈(Stack)?什么是堆(Heap)?栈和堆的区别是什么?

原参考地址&#xff1a;https://zhidao.baidu.com/question/36918441.html 栈&#xff1a;由操作系统自动分配释放 &#xff0c;存放函数的参数值&#xff0c;局部变量du的值等。其操作方式类似于数据结构中的栈&#xff1b; 堆&#xff1a; 一般由程序员分配释放&#xff0c…

堆和栈的概念和区别

在说堆和栈之前&#xff0c;我们先说一下JVM&#xff08;虚拟机&#xff09;内存的划分&#xff1a; Java程序在运行时都要开辟空间&#xff0c;任何软件在运行时都要在内存中开辟空间&#xff0c;Java虚拟机运行时也是要开辟空间的。JVM运行时在内存中开辟一片内存区域&#x…