作者:凌杰林
简介
临界资源:同一时间只能由一个进程访问的资源
临界区:访问临界资源的代码段
读者写者问题:存在一个多个进程共享的数据区(临界资源),该数据区可以是一个文件或者一块内存空间,甚至可以是一组寄存器;有些进程reader只读取这个数据区的数据,有些进程writer只往数据区中写数据。此外,还需要满足以下条件:
1.任意数量的读进程可以同时读这个文件。
2.一次只能有一个写进程可以写这个文件。
3.若一个写进程正在写文件,则禁止任何读进程读文件。
4.读者优先:必须等所有读进程读完才可以写。
也即是说,读进程不排斥其它读进程,而写进程排斥其它所有进程,包括读进程和写进程。
分析
类的编写
Semaphore类
利用Java的多线程编程实现,操作系统中的p,v操作。p,v操作的函数添加synchronized关键字,加锁,确保同一时刻只有一个进程可以操作semValue这个临界资源,否则多个进程同时访问该值,会出现错误。
public class Semaphore{
private int semValue;
//定义信号量
public Semaphore(int semValue) {
this.semValue = semValue;
}
public synchronized void p() {
semValue--;
if (semValue < 0) {
try {
this.wait();//阻塞该进程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void v(){
semValue++;
if (semValue <= 0) {
this.notify();//唤醒被阻塞的进程
}
}
}
Reader类
定义读者类,实现Runnable接口,重写run方法。
class Reader implements Runnable{
private Semaphore rmutex,wmutex;
My my;
static int readcount = 0;
public Reader(Semaphore rmutex,Semaphore wmutex,My my){
this.rmutex = rmutex;
this.wmutex = wmutex;
this.my = my;
}
@Override
public void run() {
try{
Thread.sleep((int)(1000*my.arrive_time));
}catch (InterruptedException e){e.printStackTrace();}
System.out.println("线程" + my.thread + "申请读操作");
rmutex.p();
if (readcount == 0) wmutex.p();
readcount++;
rmutex.v();
System.out.println("线程" + my.thread + "开始读操作");
try{
Thread.sleep((int)(1000*my.operate_time));
} catch (InterruptedException e){
e.printStackTrace();
}
rmutex.p();
readcount--;
System.out.println("线程" + my.thread + "结束读操作");
if (readcount == 0) wmutex.v();
rmutex.v();
}
}
细节
实现读者优先,定义readcount变量,初始值为0,用于记录当前读者进程的数量,第一个读者负责上锁,防止写进程申请资源,最有一个读进程负责解锁,唤醒被阻塞的写进程。
Writer类
定义写者类,实现Runnable接口,重写run方法。
class Writer implements Runnable{
private Semaphore wmutex;
My my;
public Writer(Semaphore wmutex,My my){
this.wmutex = wmutex;
this.my = my;
}
@Override
public void run() {
try{
Thread.sleep((int)(1000*my.arrive_time));
}catch (InterruptedException e){e.printStackTrace();}
System.out.println("线程" + my.thread + "申请写操作");
wmutex.p();
System.out.println("线程" + my.thread + "开始写操作");
try{
Thread.sleep((int)(1000*my.operate_time));
}catch (InterruptedException e){e.printStackTrace();}
wmutex.v();
System.out.println("线程" + my.thread + "完成写操作");
}
}
其他类
该类定义了一个进程的编号,到达时间,读/写类型,要求服务时间。
class My
{
int thread;
String type;
double arrive_time;
double operate_time;
My(String s)
{
StringTokenizer st=new StringTokenizer(s);
thread = Integer.parseInt(st.nextToken());
type = st.nextToken();
arrive_time = Double.parseDouble(st.nextToken());
operate_time = Double.parseDouble(st.nextToken());
}
该类是主类
import java.io.*;
import java.util.*;
public class ReaderWriter
{
public static void main(String[] args)
{
Semaphore wmutex = new Semaphore(1);
Semaphore rmutex = new Semaphore(1);
int i;
String s=null;
My m[]=new My[10];
try{
BufferedReader br=new BufferedReader(new FileReader("Input.txt"));
for (i=0;(s=br.readLine())!=null;i++)
{
m[i]=new My(s);
System.out.println("线程"+m[i].thread+"是"+m[i].type+"线程,第"+m[i].arrive_time+"秒申请读写操作,操作持续"+m[i].operate_time+"秒");
if (m[i].type.equals("R")){
new Thread(new Reader(rmutex,wmutex,m[i])).start();
}
else {
new Thread(new Writer(wmutex,m[i])).start();
}
}
}
catch(IOException e){}
}
}
数据格式
1 R 1 5
2 W 3 4
数据存储在同目录下的input.txt文件中。
源代码
public class Semaphore{
private int semValue;
public Semaphore(int semValue) {
this.semValue = semValue;
}
public synchronized void p() {
semValue--;
if (semValue < 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void v(){
semValue++;
if (semValue <= 0) {
this.notify();
}
}
}
//另外一个类
import java.io.*;
import java.util.*;
public class ReaderWriter
{
public static void main(String[] args)
{
Semaphore wmutex = new Semaphore(1);
Semaphore rmutex = new Semaphore(1);
int i;
String s=null;
My m[]=new My[10];
try{
BufferedReader br=new BufferedReader(new FileReader("Input.txt"));
for (i=0;(s=br.readLine())!=null;i++)
{
m[i]=new My(s);
System.out.println("线程"+m[i].thread+"是"+m[i].type+"线程,第"+m[i].arrive_time+"秒申请读写操作,操作持续"+m[i].operate_time+"秒");
if (m[i].type.equals("R")){
new Thread(new Reader(rmutex,wmutex,m[i])).start();
}
else {
new Thread(new Writer(wmutex,m[i])).start();
}
}
}
catch(IOException e){}
}
}
class Reader implements Runnable{
private Semaphore rmutex,wmutex;
My my;
static int readcount = 0;
public Reader(Semaphore rmutex,Semaphore wmutex,My my){
this.rmutex = rmutex;
this.wmutex = wmutex;
this.my = my;
}
@Override
public void run() {
try{
Thread.sleep((int)(1000*my.arrive_time));
}catch (InterruptedException e){e.printStackTrace();}
System.out.println("线程" + my.thread + "申请读操作");
rmutex.p();
if (readcount == 0) wmutex.p();
readcount++;
rmutex.v();
System.out.println("线程" + my.thread + "开始读操作");
try{
Thread.sleep((int)(1000*my.operate_time));
} catch (InterruptedException e){
e.printStackTrace();
}
rmutex.p();
readcount--;
System.out.println("线程" + my.thread + "结束读操作");
if (readcount == 0) wmutex.v();
rmutex.v();
}
}
class Writer implements Runnable{
private Semaphore wmutex;
My my;
public Writer(Semaphore wmutex,My my){
this.wmutex = wmutex;
this.my = my;
}
@Override
public void run() {
try{
Thread.sleep((int)(1000*my.arrive_time));
}catch (InterruptedException e){e.printStackTrace();}
System.out.println("线程" + my.thread + "申请写操作");
wmutex.p();
System.out.println("线程" + my.thread + "开始写操作");
try{
Thread.sleep((int)(1000*my.operate_time));
}catch (InterruptedException e){e.printStackTrace();}
wmutex.v();
System.out.println("线程" + my.thread + "完成写操作");
}
}
class My
{
int thread;
String type;
double arrive_time;
double operate_time;
My(String s)
{
StringTokenizer st=new StringTokenizer(s);
thread = Integer.parseInt(st.nextToken());
type = st.nextToken();
arrive_time = Double.parseDouble(st.nextToken());
operate_time = Double.parseDouble(st.nextToken());
}
}
小结
读者-写者问题为我们解决互斥问题提供了一种参考思路,核心的思想时,在于设置一个计数器readcount记录当前正在访问共享文件的进程数目,用readcount的值来判断是否为最后一个或者最先一个读者进程,从而实现加锁或者解锁。另外,对readcount的检查和赋值操作要一气呵成,就是“原子性”,这时候,我们就应该想到要用操作系统课中的P,V操作来实现,设置好互斥的相关操作,构造好该信号量的类。