(c)程序截图
创建一个线程在70时中止,等待500后唤醒。图中可以看出代码正确
4 Communicator
利用条件变量编写发送和接收一个消息来实现Communicator类的speak()和listen()方法。speak()要自动等待listen()被调用,然后将消息传递给listen()。同样的listen()也要自动等待speak()被调用,将消息传递给自己才能收到一个消息。只有消息从speak()传递到了listen()两个线程才能返回。在只有一个Communicator对象时也能工作起来,不能拥有缓冲区,也就是说listen()和speak()只有成功配对之后才能传递消息。 (a)设计思想 speak():先获得锁,然后进行判断,如果没有听者等待,就要把说者放入队列然后睡眠。如果有听者等待,就要唤醒一个听者,然后传递消息,最后释放锁。
listen():先获得锁,然后进行判断,如果没有说者等待,就要把听者放入队列然后睡眠。如果有说者等待,就要唤醒一个说者,然后传递消息,最后释放锁。 (b)
public Communicator() { lock=new Lock();
queue=new LinkedList
speakercount=0; listenercount=0; }
public void speak(int word) {
boolean intStatus = Machine.interrupt().disable(); lock.acquire();
if(listenercount==0) {
speakercount++; queue.offer(word); speaker.sleep(); listener.wake(); speakercount--; } else
{queue.offer(word); listener.wake(); }
lock.release();
Machine.interrupt().restore(intStatus); return; }
public int listen() {
boolean intStatus = Machine.interrupt().disable(); lock.acquire();
if(speakercount!=0) {speaker.wake(); listener.sleep(); } else
{listenercount++; listener.sleep(); listenercount--; }
lock.release();
Machine.interrupt().restore(intStatus); return queue.poll(); }
(c) 程序截图
线程1和线程2分别调用speak()将20,30传递,线程3和线程4调用listen()将消息接收。线程1和线程2执行时没有听者,双双等待,证明程序在多个说者和听者时也能工作的很好
5 PriorityScheduler类
通过完成实现PriorityScheduler优先级调度策略。所有的调度程序都是继承自Scheduler类,所以必须实现getPriority(), getEffectivePriority()和setPriority()方法。在调度中必须选择有效优先级最长的执行,如果有效优先级相同的线程都在等待要选择等待时间最长的。解决优先级问题的关键就是优先级反转,当一个高优先级的线程等待一个低优先级的线程时,高优先级的线程就必须把自己的有效优先级捐献给低优先级的线程,让低优先级的线程提高优先级可以尽快执行。解决这个问题的关键就是计算有效优先级,但是计算时间不能太长,所以在改变优先级的时候在计算比较合适。优先级在捐献之后不会丢失。优先级可以不断传递下去。
(a) 设计思想
在引入优先级的同时就需要引入一个ThreadState对象,它用来把线程和优先级绑定在一起。而且内部还有一个队列用来记录等待它的线程。在执行getEffectivePriority()方法时,要遍历整个队列,将等待它线程的最高有效优先级取出,和自己的优先级比较,把最大的当成自己的最高有效优先级。而在遍历的过程中,如果线程还有等待的线程还要计算自己的有效优先级,所以这是一个递归搜索的过程。而在队列类中最重要的就是nextThread()方法,它返回下一个要执行的线程。这个方法通过遍历队列,计算出所有线程的有效优先级,取出有效优先级最大的线程执行。 (b) 源代码
public int getEffectivePriority() { effectivepriority=-1;
for(int i=0;i {if(waitQueue.waitQueue.get(i).getEffectivePriority()>effectivepriority) effectivepriority=waitQueue.waitQueue.get(i).getEffectivePriority(); } if(effectivepriority>priority) setPriority(effectivepriority); return priority; } public KThread nextThread() { Lib.assertTrue(Machine.interrupt().disabled()); int max=-1; index=0; ThreadState state=null,temp=null; while((temp=pickNextThread())!=null) {if(temp.getEffectivePriority()>max) {state=temp; max=temp.getEffectivePriority();} } if(state==null) {return null; } else {return waitQueue.remove(waitQueue.indexOf(state)).thread;} } protected ThreadState pickNextThread() { if(index return waitQueue.get(index-1);} return null; } (c) 程序截图 创建三个线程thread1,thread2,thread3,分别赋予优先级为2,4,6,thread3中调用了thread1.join()。运行之后,thread1得到了thread3的优先级,有效优先级最高,最先执行,当thread1执行完成之后,thread3的优先级最高,第二执行,thread2优先级最低最后执行。 6 Boat 使用条件变量解决过河问题。O岛有一群人要到M岛去,但是只有一艘船,这艘船一次只能带一个大人或者两个孩子。开始必须假设至少有两个孩子(否则问题无法解决),每一个人都会划船。要为每个大人和孩子创建一个线程,这个线程就相当于一个人,他们能自己判断(思考),什么时候可以过河,而不是通过调度程序的调度。在解决问题的过程中不能出现忙等待,而且问题最终一定要解决。不符合逻辑的事情不能发生,比如在这个岛的人能知道对面岛的情况。 (a) 设计思想 创建两种类型的线程大人线程和孩子线程,他们分别执行不同的行为。船设置成为条件变量,人的位置,该大人还是孩子走,船是否在一个岛上设置成为布尔变量,将问题建模为数学问题。 大人进程:首先要获得锁,然后进行判断是否是大人要走,船是否在这个岛上,如果不成立大人就要等待直到成立然后大人过河,改变变量的值,到达对岸之后唤醒一个孩子把穿开回来 孩子进程:首先要获得锁,首先判断是在哪个岛上;若在O岛,则判断运输过程是否已经结束,要是结束就不执行任何操作,要是没结束,判断是否是孩子走,船是否在这个岛上,如果不成立,孩子就要等待直到成立,两个孩子过河,改变变量的值,并且还要把是否结束的信息传递过来。如果结束不做任何操作,否则唤醒一个孩子进程把船开回去;若在M岛,则孩子直接过河,并且根据获得信息要求下一次是大人还是孩子过河。 (b) 源代码 public static void begin(int adults, int children, BoatGrader b) { bg = b; parentThread = KThread.currentThread(); for (int i = 0; i < adults; i++) new KThread(new Runnable() { public void run() { AdultItinerary(); } }).fork(); for (int i = 0; i < children; i++) new KThread(new Runnable() { public void run() { ChildItinerary(); } }).fork(); children_number_Oahu = children; adult_number_Oahu = adults; children_number_Molokai = 0;