要求
一、实验目的
1、熟悉多线程编程
2、熟悉使用信号量机制解决进程同步问题
二、实验内容
创建一个包含n 个线程的控制台进程。用这n 个线程来表示n个读者或写者。每个线程按相应测试数据文件的要求,进行读写操作。请用信号量机制分别实现读者优先和写者优先的读者-写者问题。
读者优先:如果一个读者申请进行读操作时已有另一读者正在进行读操作,则该读者可直接开始读操作。
写者优先:如果一个读者申请进行读操作时已有另一写者在等待访问共享资源,则该读者必须等到没有写者处于等待状态后才能开始读操作。
三、实验条件
1、为每个学生提供一台具有WINDOWS 2000/NT/XP操作系统的计算机;
2、实验机器要求安装Visual C 6.0编程平台;
3、实验要求一人一机。
四、 运行结果显示要求
1、要求在每个线程创建、发出读写操作申请、开始读写操作和结束读写操作时分别显示一行提示信息,以确信所有处理都遵守相应的读写操作限制。
2、测试数据文件格式:测试数据文件包括n 行测试数据,分别描述创建的n 个线程是读者还是写者,以及读写操作的开始时间和持续时间。每行测试数据包括四个字段,各字段间用空格分隔。第一字段为一个正整数,表示线程序号。第二字段表示相应线程角色,R 表示读者是,W表示写者。第三字段为一个正数,表示读写操作的开始时间。线程创建后,延时相应时间(单位为秒)后发出对共享资源的读写申请。第四字段为一个正数,表示读写操作的持续时间。当线程读写申请成功后,开始对共享资源的读写操作,该操作持续相应时间后结束,并释放共享资源。下面是一个测试数据文件的例子:
1 R 3 5
2 W 4 5
3 R 5 2
4 R 6 5
理解
上面是实验要求。下面讲讲自己的体会:
- 首先要明白在多线程编程中的
互斥关系
——读写互斥
,写写互斥
。两个优先方式都遵从这个出发点。 - 在满足以上的条件下,进来的线程都好像是在
排队
,然后看当前谁优先。 - 自己
如果是优先
的,那么可以直接插队
。再考虑正在执行的是否和自己阻塞,如果正在执行的和自己阻塞,那自己也得排队,只不过排在前面。(类似写者优先的写写线程)如果正在执行的和自己不是阻塞的,那么自己就可以直接进去执行(读者优先的读读进程)。 - 如果自己
不是优先
的,那么自己只能老老实实的排队
,就算自己前面有和自己不互斥的线程执行也不行。类似写者优先的(读线程在执行,新的写进程等待资源,更新的读线程只能等写进程释放,如果有新的写进程进来,还可以插队,——他只能等待队列中所有在等待的写进程释放完毕再执行自己。)
- 对于
读者优先
:
读者就是优先的。假设a,b都是同时请求,但是a是读者那么a优先使用资源,还有一点很重要的就是读者优先的读者可以并行执行。而写着只能单线程执行。在执行过程中,只要阻塞的写者在等待过程中有新的读者进来那么他要等待所有读者完成才能自己释放自己。
- 对于
写者优先
:
无疑所有写的操作是优先的,这个过程可能会产生大量阻塞,因为相对较快(本来可以并行的读者被大量阻塞)。如果资源中没有写者那么读者依然可以并行,但是一旦出现写者在等待读者资源,那么新的读者就不能在并行执行,要等待所有写者执行完毕才可执行读者。
对于多线程编程的注意点
有:
- 读者优先和写者优先是两个不同的策略方法,方法有相似之处但是也有很大不同,函数需要分开完成。
- 最主要的排序方式基于时间排序,次要的排序以读者还是写者谁优先为准则
- 读者优先或者写者优先的阻塞会导致线程开始时间的变化。而不过采用双队列一个存进入时间的排序,一个存结束时间的排序,修改其中的一个会影响另一个队列中元素值不错,但是如果不对另一个队列进行增/删是不会触发堆排序的功能(挺重要的)。
- 可能有些阻塞时候的等待时间和开始时间改变处理比较复杂,要考虑当前是读致使阻塞,还是写致使阻塞,还是前面有写的资源再等待致使阻塞。要用多个变量维系系统使得正确的更改线程的阻塞时间。
我的大体思路(水平有限,不一定很准确):
代码
下面附上我的ac代码
package 实验; import java.util.Comparator; import java.util.PriorityQueue; import java.util.Queue; import java.util.Scanner; public class morethread { static Scanner sc=new Scanner(System.in); static Queue<thread>q1; static Queue<thread>end; static thread thread[]; static int time=0; private static void threadcreate(int n) {//创建线程 thread=new thread[n]; for(int i=0;i<n;i++) { System.out.println("请输入线程"+(i+1)+"的相关属性(id,r/w,intime,timelong)"); int id=sc.nextInt(); String type=sc.next(); int start=sc.nextInt(); int chixu=sc.nextInt(); thread [i]=new thread(id, type, start, chixu); System.out.println("线程"+id+" 操作:"+type+" 开始时间:"+start+" 持续时间:"+chixu); } } private static void writerthread(int n) {//写者优先 // TODO 自动生成的方法存根 q1=new PriorityQueue<thread>(comstart2); end=new PriorityQueue<thread>(comend); for(int i=0;i<n;i++) { q1.add(thread[i]); end.add(thread[i]); } int timeend=-1;//读结束的时间最晚 int writendtime=0;//写操作结束的时间 thread start=q1.peek();thread finish=end.peek(); boolean iswait=false;//是否有write操作在等待 while(!q1.isEmpty()||!end.isEmpty()) { if(!q1.isEmpty()) {start=q1.peek();} if(!end.isEmpty()) {finish=end.peek();} if(start.starttime<(finish.starttime+finish.totaltime)&&!q1.isEmpty())//可以开始读写操作 { thread team=q1.poll(); if(team.type.equals("w"))//写操作 { if(team.starttime>=writendtime)//没有写操作在前面 { if(team.starttime<timeend)//需要等待 更改改点信息重新pull入队列 { iswait=true;//声明之后其他读操作就不能再直接进行 team.starttime=timeend; q1.add(team); end.remove(team);end.add(team); } else {//不需要等待,直接开始 System.out.println("线程"+team.id+" 开始:"+team.type+"操作 时间:"+team.starttime+" 结束时间"+(team.starttime+team.totaltime)); writendtime=team.starttime+team.totaltime;//更新写操作的结束时间 iswait=false;//更新等待点 } } else//有正在进行的操作,需要等待 { iswait=true; team.starttime=writendtime; q1.add(team); end.remove(team);end.add(team);//移除然后增加才可以重新排序 } } else//读操作 { if(iswait) {//不清楚他在等待读操作还是写操作,反正它一定不能在下面两个操作之前完成,所以更新节点道值最大的 int time2=timeend; if(time2<writendtime)time2=writendtime; team.starttime=time2; q1.add(team); end.remove(team);end.add(team); } else//没有等待点 { if(team.starttime<writendtime)//有写操作,读需要等待 将这个点从新弄道队列里面 { team.starttime=writendtime; q1.add(team); end.remove(team);end.add(team); } else//可以释放了 { System.out.println("线程"+team.id+" 开始:"+team.type+"操作 时间:"+team.starttime+" 结束时间"+(team.starttime+team.totaltime)); if(team.starttime+team.totaltime>timeend)//如果可以更新时间 { timeend=team.starttime+team.totaltime; } } } } } else { thread team=end.poll(); System.out.println("线程"+team.id+" 结束:"+team.type+" 结束时间"+(team.starttime+team.totaltime)); } } } private static void readerthread(int n)//读者优先 { q1=new PriorityQueue<thread>(comstart); end=new PriorityQueue<thread>(comend); for(int i=0;i<n;i++) { q1.add(thread[i]); end.add(thread[i]); } int timeend=-1;//读结束的时间最晚 int writendtime=0;//写操作结束的时间,如果有写的状态,那么 thread start=q1.peek();thread finish=end.peek(); while(!q1.isEmpty()||!end.isEmpty()) { if(!q1.isEmpty()) {start=q1.peek();} if(!end.isEmpty()) {finish=end.peek();} if(start.starttime<=(finish.starttime+finish.totaltime)&&!q1.isEmpty())//可以开始读写操作 { thread team=q1.poll(); if(writendtime>start.starttime)//此时有写的情况,需要等待阻塞,也就是将此队列头抛出修改开始时间然后再次入队 { team.starttime=writendtime; q1.add(team); end.remove(team);end.add(team); } else {//此时无写的操作,无操作或者读操作 //System.out.println("线程"+team.id+" 开始:"+team.type+"操作 时间:"+team.starttime+" 结束时间"+(team.starttime+team.totaltime)); if(team.type.equals("w"))//写的情况,写需要检查是否阻塞自己如果有读的情况则需要阻塞自己 { int time2=timeend; if(time2<writendtime)time2=writendtime; if(team.starttime>=time2)//在这个时间前没有任何读或者写的操作,上锁,等于号一定有,因为都抛出它了,他的优先级一定最高 { writendtime=team.starttime+team.totaltime; System.out.println("线程"+team.id+" 开始:"+team.type+"操作 时间:"+team.starttime+" 结束时间"+(team.starttime+team.totaltime)); } else {//状态还有读操作,需要阻塞 team.starttime=time2; q1.add(team); end.remove(team);end.add(team); } } else//读的情况 { System.out.println("线程"+team.id+" 开始:"+team.type+"操作 时间:"+team.starttime+" 结束时间"+(team.starttime+team.totaltime)); if(team.starttime+team.totaltime>timeend) { timeend=team.starttime+team.totaltime; } } } } else if(!end.isEmpty()){//某个结束操作在这个时间段 thread team=end.poll(); System.out.println("线程"+team.id+" 结束:"+team.type+" 结束时间"+(team.starttime+team.totaltime)); } } } public static void main(String[] args) { System.out.println("请输入n个线程程序"); int n=sc.nextInt(); threadcreate(n); System.out.println("请输入数字选择读者优先或者写者优先\n1:读者优先\n2:写者优先"); int index=sc.nextInt(); while(index<1||index>2) {System.out.println("请输入正确的数字");index=sc.nextInt();} if(index==1)//读者优先 { readerthread(n); } else { writerthread(n); } } static Comparator<thread>comstart=new Comparator<thread>() {//读者优先的comparator接口,优先时间,时间相同则先返回 public int compare(thread o1, thread o2) { // TODO 自动生成的方法存根 if(o1.starttime==o2.starttime) { return (int)(o1.type.charAt(0)-o2.type.charAt(0));// r w 先r后w } else return o1.starttime-o2.starttime;//返回小根堆 } }; static Comparator<thread>comstart2=new Comparator<thread>() {//写者优先的 public int compare(thread o1, thread o2) { // TODO 自动生成的方法存根 if(o1.starttime==o2.starttime) { return (int)(o2.type.charAt(0)-o1.type.charAt(0));// w r 先w后r } else return o1.starttime-o2.starttime;//返回小根堆 } }; static Comparator<thread>comend=new Comparator<thread>() { public int compare(thread o1, thread o2) { return (int)((o1.starttime+o1.totaltime)-(o2.starttime+o2.totaltime)); } }; static class thread { int id; String type; int starttime; int totaltime; public thread(int id,String type,int starttime,int totaltime) { this.id=id; this.type=type; this.starttime=starttime; this.totaltime=totaltime; } } }
测试用例:
读者优先:
写者优先:
本人水平有限,?,如果有大佬发现错了,请指正。