一、Project1实验描述
4.1 实现KThread.join()
4.1.1 要求分析
实现Implement KThread.join()函数。其它的线程不必调用join函数,但是如果join()函数被调用的话,也只能被调用一次。对join()函数第二次调用的执行结果是不被定义的,即使第二次调用的线程与第一次调用的线程不同。无论有没有被join,一个进程都能够正常结束。
4.1.2 设计方案
为每个线程创建一个等待线程队列,当线程执行join方法时,将当前执行的线程加入到自己的等待队列中,并且使当前线程休眠,在执行join方法的线程结束后被唤醒。
本题需要注意的是:join方法只能调用一次,用Lib.assertTrue(this != currentThread);来实现;调用join方法的线程结束后需要将其等待队列中的线程全部唤醒,用while((thread=currentThread().waitQueue.nextThread()) != null) {thread.ready();}在finish()中实现,finish函数在run函数返回时自动调用,也可以被直接调用,但是当前唯一运行的线程不能直接被finish,只有当其他线程运行时才可结束。
4.1.3 实现代码
public void join() {
Lib.debug(dbgThread, "Joining to thread: " + toString());
Lib.assertTrue(this != currentThread);// 判断是否是当前线程
boolean status = Machine.interrupt().disable();
if (this.status != statusFinished) {
// 如果当前线程还没执行完
waitQueue.waitForAccess(KThread.currentThread());// 当前线程加入等待队列
sleep(); // 当前线程休眠,等待join线程完毕后被唤醒
}
Machine.interrupt().restore(status);
}
public static void finish() {
Lib.debug(dbgThread,"Finishingthread:"+currentThread.toString());// 调试
Machine.interrupt().disable();
Machine.autoGrader().finishingCurrentThread();// 告诉TCB当前线程结束了
Lib.assertTrue(toBeDestroyed == null);
toBeDestroyed = currentThread;
currentThread.status = statusFinished;// 当前线程状态置为结束
KThread thread;
while((thread=currentThread().waitQueue.nextThread()) != null) {
thread.ready();// 取出就绪队列所有线程
}
sleep();// 将当前线程置为完成态,读取下一个就绪线程
}
4.1.4 测试代码及结果
本题我简单的创建了两个进程A,B,首先执行B,在执行B的过程中对A执行join方法,因此B被挂起,A开始循环执行,等到A执行完毕,B才会返回执行并结束。
public class KThreadTest {
public KThreadTest(){
}
public static void simpleJoinTest(){
KThread A_thread = new KThread(new KThreadTest.A_thread(5));
KThread B_thread = new KThread(new KThreadTest.B_thread(A_thread));
B_thread.fork();
B_thread.join();
}
public static class B_thread implements Runnable{
B_thread(KThread joinee){
this.joinee=joinee;
}
public void run(){
System.out.println("B is ready");
System.out.println("forking and joining A...");
this.joinee.fork();
this.joinee.join();
System.out.println("B is end");
}
private KThread joinee;
}
public static class A_thread implements Runnable{
A_thread(int num){
this.num = num;
}
public void run(){
System.out.println("A is ready");
System.out.println("A is going on");
for(int i=0;i<this.num;i++){
System.out.println("A loops"+i+"times");
KThread.currentThread().yield();
}
System.out.println("A is end");
}
private int num;
}
}
4.2 实现条件变量
4.2.1 要求分析
利用中断有效和无效所提供的原子性直接实现条件变量。我们已经提供类似的例子实例实现信号量。你要按此提供类似的条件变量的实现,不能直接利用信号量来实现(你可以使用lock,虽然它间接地调用了信号量)。在你完成时要提供条件变量的两种实现方法。你的第二种条件变量实现要放在nachos.threads.Condition2中。
4.2.2 设计方案
本题要求实现线程对于全局变量的共享机制,实现线程的并发执行。具体在nachos中要求实现的是sleep(),wake()和wakeAll()方法,为了保证只有一个线程可以进入临界区,我使用了thread包中的Lock类,而每一个条件变量都会与一个锁变量共同使用。
在sleep函数中需要注意的是要在函数的开始关闭中断直到结束再打开以保障函数的原子性,而在线程执行sleep阻塞自己之后,等待wake唤醒,之后立即请求锁,因此在sleep函数结束时线程会再次拥有锁。wake与wakeall原理类似,都是调用KThread.ready()方法将线程加入到就绪队列,不同点是wakeall要对等待队列中的所有线程进行唤醒,因此需要KThread thread=waitqueue.nextThread();来对等待队列进行遍历。Wakeall有两种实现方式,一种是遍历等待队列执行KThread.ready()来唤醒,另一种依赖于wake(),可以直接在等待队列中执行wake()来进行唤醒。
4.2.3 实现代码
public void sleep(){
Lib.assertTrue(conditionLock.isHeldByCurrentThread());//确定当前进程拿着锁
boolean status=Machine.interrupt().disable();
conditionLock.release();//释放锁
waitqueue.waitForAccess(KThread.currentThread());//当前进程加入到等待队列
KThread.currentThread().sleep();
conditionLock.acquire();
Machine.interrupt().restore(status);
}
public void wake() {
Lib.assertTrue(conditionLock.isHeldByCurrentThread());
boolean status=Machine.interrupt().disable();
KThread thread=waitqueue.nextThread();//取等待队列中的一个线程
if (!(thread==null))
thread.ready();//不为空则唤醒
Machine.interrupt().restore(status);
}
public void wakeAll() {
Lib.assertTrue(conditionLock.isHeldByCurrentThread());
boolean status=Machine.interrupt().disable();
KThread thread=waitqueue.nextThread();
while(!(thread==null))
{ thread.ready();
thread=waitqueue.nextThread();
//wake();
}
Machine.interrupt().restore(status);
}
private Lock conditionLock;
PrivateT hreadQueue waitqueue=ThreadedKernel.scheduler.newThreadQueue(true);
4.2.4 测试代码及结果
本题的测试我采用了模拟银行账户的方式,建立两个账户后将其sleep,然后创建第三个进程使用wakeall将其全部唤醒,之后两个进程可以交替向账户中存钱,最后打印出临界区账户的余额。
public class Condition2Test {
Lock conlock = new Lock();
Condition2 c2test =new Condition2(conlock);//c2test为共有条件变量
public Condition2Test(){
}
public void simpleCondition2Test(){
System.out.println("\n ***Condition2Test is now executing. ***");
System.out.println("first 10000");
final MyCount myCount = new MyCount("00001",10000);
KThread thread1 = new KThread(new Runnable(){
public void run(){
new SaveThread("hhh",myCount,2000);
System.out.println("hhh goes to sleep");
conlock.acquire();
c2test.sleep();
System.out.println("hhh reacquires lock when woken.");
conlock.release();
System.out.println("hhh is awake!");
myCount.save(2000,"hhh");
}
});
KThread thread2 = new KThread(new Runnable(){
public void run(){
new SaveThread("yyy",myCount,3000);
System.out.println("yyy goes to sleep");
conlock.acquire();
c2test.sleep();
System.out.println("yyy reacquires lock when woken.");
conlock.release();
System.out.println("yyy is awake!");
myCount.save(3000,"yyy");
}
});
KThread thread3 = new KThread(new Runnable(){
public void run(){
System.out.println("thread3 waking up the thread");
conlock.acquire();
c2test.wakeAll();
conlock.release();
System.out.println("hhh and yyy woke up by wakeAll");
}
});
thread1.fork();
thread2.fork();
thread3.fork();
//thread1.join();
thread1.join();
thread2.join();
thread3.join();
// thread2.yield();
System.out.println("***Condition2Test finished.***\n");
}
}
class SaveThread extends KThread{
private String name;
private MyCount myCount;
private int x;
SaveThread(String name,MyCount myCount,int x){
this.name = name;
this.myCount = myCount;
this.x = x;
}
public void run(){
myCount.save(x,name);
}
}
class MyCount{
private String id;
private int cash;
Lock lock = new Lock();
Condition2 c2test = new Condition2(lock);
MyCount(String id,int cash){
this.id = id;
this.cash = cash;
}
public void save(int x,String name){
lock.acquire();
if(x>0){
cash+=x;
System.out.println(name +"save"+x+"mount is"+cash);
}
lock.release();
}
}
4.3 完成Alarm类
4.3.1 要求分析
完成Alarm类,通过waitUntil(long x)方法实现。一个线程通过调用waitUntil(long x)方法将自己挂起,一直到经过x时间再被唤醒。这对现实操作很有用,例如,光标的周期闪烁。线程经过x时间后唤醒,但不需要立刻运行,而是加入readyqueue中。不建立任何多余的线程实现waitUntil(),只需要修改waitUntil()中断处理程序和计时器。waitUntil()不受限于任何一个线程,任何线程可以调用它实现被挂起。
4.3.2 设计方案
与alarm有关的是machine.timer类,它在每500个时钟周期左右调用回调函数alarm.timerinterrupt()。因此要在timerinterrupt函数中对等待队列中的线程进行遍历,选取其中唤醒时间小于当前时间的线程进行唤醒加入就绪队列。另外要实现waituntil(x),实现对线程等待时间x的设置,并且维护一个二元组队列(线程,唤醒时间),队列按照唤醒时间进行排序,唤醒时间靠前的排在队列前端,这样做虽然在插入线程时需要遍历队列寻找插入的地方,增加了一定的时间复杂度,但是在唤醒时不必全部遍历队列。
4.3.3 实现代码
public Alarm() {
Machine.timer().setInterruptHandler(new Runnable() {
public void run() { timerInterrupt(); }
});
}
public void timerInterrupt() { //
boolean status = Machine.interrupt().disable();
long currenttime = Machine.timer().getTime();//当前时间
int size = linkedlist.size();
if (size == 0)//将等待队列上应当唤醒的进程加入到就绪队列上
;
else
while (!linkedlist.isEmpty())
if (linkedlist.peek().getWakeTime() <= Machine.timer().getTime())
linkedlist.poll().thread.ready();
else
break;
KThread.yield();
Machine.interrupt().restore(status);
}
public void waitUntil(long x) {
// for now, cheat just to get something working (busy waiting is bad)
boolean status = Machine.interrupt().disable();
long waketime = Machine.timer().getTime() + x;
KThreadWakeTime kthreadwaketime = new KThreadWakeTime(
KThread.currentThread(), waketime);
int size = linkedlist.size();
if (size == 0)
linkedlist.add(kthreadwaketime);
else
for (int i = 0; i < size; i++) {
if (waketime < linkedlist.get(i).getWakeTime()) {
linkedlist.add(i, kthreadwaketime);//将线程加入到等待队列上
break;
}
if (i == size - 1
&& waketime >= linkedlist.get(i).getWakeTime())
linkedlist.add(i + 1, kthreadwaketime);
}
KThread.sleep();
Machine.interrupt().restore(status);
}
public class KThreadWakeTime {
private KThread thread = null;
private long waketime = 0;
public KThreadWakeTime(KThread thread, long waketime) {
this.thread = thread;
this.waketime = waketime;
}
public KThread getThread() {
return thread;
}
public long getWakeTime() {
return waketime;
}
}
LinkedList<KThreadWakeTime> linkedlist = new LinkedList();
}
4.3.4 测试代码及结果
本题我创建了五个线程,依次设置睡眠时间为2000ticks,线程醒来后各自打印出睡眠开始时间,要求睡眠时间,醒来时间以及实际睡眠时间。这样测试可以看出在执行回调函数时并没有执行忙等待,而是一段时间之后询问一次,因此会有一定的时间差。
public static void selfTest_Alarm(int numOfTest){
Runnable a = new Runnable(){
public void run(){
AlarmTestThread();
}
};
for(int i=0;i<numOfTest;i++){
int numOfThreads=5;
System.out.println("creating"+numOfThreads+"num of threads");
for(int j=0;j<numOfThreads;j++){
KThread thread = new KThread(a);
thread.setName("thread");
thread.fork();
ThreadedKernel.alarm.waitUntil(30000000);
}
}
}
static void AlarmTestThread(){
long sleepTime=20000;
long timeBeforeSleep = Machine.timer().getTime();
ThreadedKernel.alarm.waitUntil(sleepTime);
long timeAfterSleep = Machine.timer().getTime();
long actualSleepTime = timeAfterSleep - timeBeforeSleep;
System.out.println(KThread.currentThread().toString() + "is ask at:"+
timeBeforeSleep +"sleep"+sleepTime +"ticks");
System.out.println(KThread.currentThread().toString() + "is ask at:"+
Machine.timer().getTime() +"sleep time is"+sleepTime +"ticks, actrully sleep:"+actualSleepTime);
}
4.4 条件变量实现同步消息
4.4.1 要求分析
使用条件变量来实现一个字长信息的发送和接收同步。使用void speak(int word) 和int listen()函数来实现通讯(Communicator)类的通讯操作。speak函数具有原子性,在相同地Communicator类中等待listen函数被调用,然后将此字发生给listen函数。一旦传送完毕,两个函数都返回(listen函数返回此字)。
4.4.2 设计方案
本题相当于一个缓冲区为0的生产者消费者问题,需要维护一个Speaker等待队列,一个listener等待队列和一个words存储队列,本题中我使用了锁和tast2中的条件变量。
需要注意的是,Speaker被唤醒的条件是listener不为空,否则将其加入等待队列;而listener被唤醒的条件是Speaker不为空且words不为空,因此要在提供words之后将listener唤醒。在执行speak和listen函数之前都要先获得锁,以保证临界区的互斥,也就是保证听说必须是交替进行,最后释放锁,并且一个听者或说者不能多次执行听说方法。
4.4.3 实现代码
public Communicator() {
lock=new Lock();
queue=new LinkedList<Integer>();//保存说者话语的队列
speaker=new Condition2(lock);
listener=new Condition2(lock);
word=0;
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() {
//先获得锁,然后进行判断尝试唤醒speaker,如果没有说者等待,就要把听者放入队列然后睡眠。如果有说者等待,就要唤醒一个说者,将自己挂起以等待speaker准备好数据再将自己唤醒,然后传递消息,最后释放锁。
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();
}
private Lock lock;
private Condition2 speaker,listener;
private int word,speakercount,listenercount;
private Queue<Integer> queue;
4.4.4 测试代码及结果
本题的测试我创建了五个听者和五个说者,现将听者和说着全部挂起,用一个重写的sleep函数实现在一定时间后自动唤醒听者和说者,此处使用了alarm类的waituntil,因为说者先被唤醒,所以说者在准备好word之后进入等待队列,在听者被调用的时候,说者被依次唤醒,听者接收到word。
public class CommunicatorTest {
public CommunicatorTest(){
Message=0;
numOfSpeakers=5;
numOfListeners=5;
communicator = new Communicator();
}
public void commTest(int num){
System.out.println("\n CommunicatorTest begin");
for(int i=0;i<num;i++){
createSpeakers(numOfSpeakers);
createListeners(numOfListeners);
System.out.println("\n speaker:"+numOfSpeakers);
System.out.println("\n listener:"+numOfListeners);
sleep(numOfSpeakers+numOfListeners);
System.out.println("\n speaker and listener has created.");
}
System.out.println("CommunicatorTest end");
}
public void sleep(int numThreadsCreated){
ThreadedKernel.alarm.waitUntil(numThreadsCreated*100);
}
public class Listener implements Runnable{
public void run(){
int messageToReceive=communicator.listen();
System.out.print(Message);
System.out.println(" "+KThread.currentThread().getName()+"receive"+messageToReceive);
}
}
public class Speaker implements Runnable{
public void run(){
communicator.speak(Message++);
System.out.print(Message);
System.out.println(" "+KThread.currentThread().getName()+"send"+Message);
}
}
public void createSpeakers(int speakers){
int j;
for(j=0;j<=speakers;j++){
KThread speakerThread=new KThread(new Speaker());
speakerThread.setName("Speaker"+j);
speakerThread.fork();
};
}
public void createListeners(int listeners){
int k;
for(k=0;k<listeners;k++){
KThread listenerThread=new KThread(new Listener());
listenerThread.setName("Listener"+k);
listenerThread.fork();
}
}
public static int MAX_THREADS=245;
private int Message;
private Communicator communicator;
private int numOfSpeakers;
private int numOfListeners;
}
4.5 实现优先级调度
4.5.1 要求分析
通过完成实现PriorityScheduler优先级调度策略。所有的调度程序都是继承自Scheduler类,所以必须实现getPriority(), getEffectivePriority()和setPriority()方法。
4.5.2 设计方案
传统的优先级调度是针对每一个线程设置一个优先级,在线程调度时,调度程序选择一个拥有最高优先级的线程加入就绪队列。然而传统的优先级调度可能会导致饥饿现象:当低优先级拿到锁进入临界区时,因为优先级低而无法执行调度,但高优先级因为无法拿到锁而无法获取临界区资源,因此程序会陷入死锁。为了避免死锁,在本题的优先级调度中实现了优先级的捐赠机制,对于每一个线程维护一个等待队列,在计算线程的有效优先级时,要取其等待队列中线程的最大优先级。
在引入优先级的同时就需要引入一个ThreadState对象,它用来把线程和优先级绑定在一起。而且内部还有一个队列用来记录等待它的线程。在
ThreadState类中增加两个LinkedList<>类表,存放PriorityQueue。一个表用来记录该线程所占用资源的优先队列,另一个表用来记录该线程所想占有的资源的优先队列。占用资源的队列作为发生优先级反转时,捐献优先级计算有效优先级的来源依据,想占有的资源的队列用来为线程声明得到资源做准备。
而在队列类中最重要的就是nextThread()方法,它返回下一个要执行的线程。这个方法通过遍历队列,计算出所有线程的有效优先级,取出有效优先级最大的线程执行。
waitForAccess()将需要等待获得资源的线程加入一个等待队列等待调度。
4.5.3 实现代码
public class PriorityScheduler extends Scheduler {
/**
* Allocate a new priority scheduler.
*/
public PriorityScheduler() {
}
public ThreadQueue newThreadQueue(boolean transferPriority) {// 分配一个线程队列
return new PriorityQueue(transferPriority);
}
public int getPriority(KThread thread) {// 得到线程的优先级
Lib.assertTrue(Machine.interrupt().disabled());
return getThreadState(thread).getPriority();
}
public int getEffectivePriority(KThread thread) {// 得到线程的有效优先级
Lib.assertTrue(Machine.interrupt().disabled());
return getThreadState(thread).getEffectivePriority();
}
public void setPriority(KThread thread, int priority) {// 设置线程优先级
Lib.assertTrue(Machine.interrupt().disabled());
Lib.assertTrue(priority >= priorityMinimum
&& priority <= priorityMaximum);// 确定优先级在有效区间内
getThreadState(thread).setPriority(priority);
}
public boolean increasePriority() {
boolean intStatus = Machine.interrupt().disable();
KThread thread = KThread.currentThread();
int priority = getPriority(thread);// 获取当前线程的优先级
if (priority == priorityMaximum)// 优先级已经最大,不可增加
return false;
setPriority(thread, priority + 1);// 优先级加一
Machine.interrupt().restore(intStatus);
return true;
}
public boolean decreasePriority() {
boolean intStatus = Machine.interrupt().disable();
KThread thread = KThread.currentThread();
int priority = getPriority(thread);// 获取当前线程的优先级
if (priority == priorityMinimum)// 优先级已经最小,不可降低
return false;
setPriority(thread, priority - 1);// 优先级减一
Machine.interrupt().restore(intStatus);
return true;
}
public static final int priorityDefault = 1; //默认
public static final int priorityMinimum = 0;
public static final int priorityMaximum = 7;
protected ThreadState getThreadState(KThread thread) {// 得到线程的优先级状态,如果线程优先级未创建则创建为默认优先级
if (thread.schedulingState == null)
thread.schedulingState = new ThreadState(thread);
return (ThreadState) thread.schedulingState;
}
/**
* A <tt>ThreadQueue</tt> that sorts threads by priority.
*/
protected class PriorityQueue extends ThreadQueue {
PriorityQueue(boolean transferPriority) {// 自动调用父类无参数构造方法,创建一个线程队列
this.transferPriority = transferPriority;
}
public void waitForAccess(KThread thread) {// 传入等待队列的线程
Lib.assertTrue(Machine.interrupt().disabled());
getThreadState(thread).waitForAccess(this);
}
public void acquire(KThread thread) {
Lib.assertTrue(Machine.interrupt().disabled());
if (!thread.getName().equals("main")) {
getThreadState(thread).acquire(this);
}
}
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 ThreadState_Queue.remove(ThreadState_Queue.indexOf(state)).thread;// 取出等待队列中优先级最大的线程
}
}
protected ThreadState pickNextThread() {// 遍历队列
if (index < ThreadState_Queue.size()) {
index++;
return ThreadState_Queue.get(index - 1);
}
return null;
}
public void print() {
Lib.assertTrue(Machine.interrupt().disabled());
// implement me (if you want)
}
public boolean transferPriority;
public LinkedList<ThreadState> ThreadState_Queue = new LinkedList<ThreadState>();//该线程join的线程队列
public ThreadState linkedthread = null;
private int index;
}
protected class ThreadState {
public ThreadState(KThread thread) {
this.thread = thread;
setPriority(priorityDefault);
waitQueue = new PriorityQueue(true);
}
public int getPriority() {
return priority;
}
public int getEffectivePriority() {// 得到有效优先级
// implement me
effectivepriority = -1;// 初始化一个int
//System.out.println("aa"+waitQueue.ThreadState_Queue.size());
for (int i = 0; i < waitQueue.ThreadState_Queue.size(); i++)
{
if (waitQueue.ThreadState_Queue.get(i).getEffectivePriority() > effectivepriority)
effectivepriority = waitQueue.ThreadState_Queue.get(i)
.getEffectivePriority();
}
if (effectivepriority > priority)
setPriority(effectivepriority);// 将最大有效优先级设置为线程本身优先级
//System.out.println(priority);
return priority;
}
public void setPriority(int priority)
{
if (this.priority == priority)
return;
this.priority = priority;
// implement me
}
public void waitForAccess(PriorityQueue waitQueue) {// 将此线程状态存入传入的等待队列
// implement me
waitQueue.ThreadState_Queue.add(this);
if (waitQueue.linkedthread != null
&& waitQueue.linkedthread != this) {
waitQueue.linkedthread.waitQueue.waitForAccess(this.thread);//加入该线程的等待队列
}
}
public void acquire(PriorityQueue waitQueue) {// 相当于一个线程持有的队列锁
// implement me
Lib.assertTrue(waitQueue.ThreadState_Queue.isEmpty());
waitQueue.linkedthread = this;
}
protected KThread thread;// 这个对象关联的线程
protected int priority;// 关联线程的优先级
protected int effectivepriority;// 有效优先级
protected PriorityQueue waitQueue;//等待该线程的线程队列
}
}
4.5.4 测试代码及结果
本题的测试我创建了四个线程,设置优先级为2,1,3,4,在正常情况下执行的顺序应当与优先级大小成反比,也就是3,4,2,1,但是我在线程三执行过程中执行了线程一的join,因此要将线程三的优先级捐赠给一,测试结果如下:
public static void selftest_PriorityScheduler() {
System.out.println("-----PriorityScheduler功能测试-----");
KThread thread1 = new KThread(new Runnable() {
public void run() {
KThread.yield();
System.out.println("线程1正在执行");
}
});
thread1.setName("thread1");
KThread thread2 = new KThread(new Runnable() {
public void run() {
KThread.yield();
System.out.println("线程2正在执行");
}
});
thread2.setName("thread2");
KThread thread3 = new KThread(new Runnable() {
public void run() {
thread1.join();
KThread.yield();
System.out.println("线程3正在执行");
}
});
thread3.setName("thread3");
KThread thread4 = new KThread(new Runnable() {
public void run() {
KThread.yield();
System.out.println("线程4正在执行");
}
});
thread1.setName("thread4");
boolean status = Machine.interrupt().disable();
ThreadedKernel.scheduler.setPriority(thread1, 2);
ThreadedKernel.scheduler.setPriority(thread2, 1);
ThreadedKernel.scheduler.setPriority(thread3, 3);
ThreadedKernel.scheduler.setPriority(thread4, 4);
Machine.interrupt().restore(status);
thread1.fork();
thread2.fork();
thread3.fork();
thread4.fork();
}
4.6 条件变量解决过河问题
4.6.1 要求分析
利用前面所学同步方法,以及条件变量解决过河问题。一群夏威夷人想要从瓦胡岛(Oahu)到摩洛凯岛(Molokai),只有一艘船,该船可载最多2个小孩或一个成人,且必须有一个开船的人(默认每个人都会开船)。设计一个解决方案,使得每个人都能从Oahu到Molokai。假设至少有两个小孩,每一个人都会划船。要为每个大人和孩子创建一个线程,这个线程就相当于一个人,他们能自己判断(思考),什么时候可以过河,而不是通过调度程序的调度。在解决问题的过程中不能出现忙等待,而且问题最终一定要解决。不符合逻辑的事情不能发生,比如在这个岛的人能知道对面岛的情况。
4.6.2 设计方案
需要记录的信息有:
1. O岛上大人/小孩的人数和M岛上大人/小孩的人数
2. 船的位置(在O岛还是M岛) 和状态(空/半满/全满) (半满指只有一个
小孩, 全满指有两个小孩或一个大人) 3. 是否最后一次运输
4. 孩子、大人信息的条件变量,并且需要一个锁 运送过程大体如下:
1:所以我们可以尽可能多的先从O岛把孩子运过来(M岛到O岛需要一个child驾驶员,要避免出现当大人到M岛上而没有孩子在M岛上出现这种情况)
2:然后开始运输大人,并且让孩子把船开回O岛 3:重复1,2
4:最后将两个孩子运到M岛
对于大人,若满足以下条件则独自乘船过河(每个大人过且仅过一次河, 线程即告结束), 否则(在O岛)等待:
1)O岛上只有一个小孩或没有小孩 2)船在O岛 3)船为空
对于小孩, 满足以下情况则乘船过河:
1)初始条件,小孩全在O岛, 船在O岛,且M岛没有小孩。
2)某小孩在O岛, 船在O岛, 船为空, O岛上的小孩数大于等于2,则该小孩上船等另外一个小孩上船后,两人一起划船过河到M。
3)某小孩在O岛, 船在O岛, 船为空, O岛上没有大人: 该小孩上船过河 4)当所有的大人运完了之后,O岛出现了两个孩子,这个时候这两个孩子划船过河。
方案:使用一个锁变量保证互斥,四个条件变量保证同步。结构如下:程序首先创建大人线程和孩子线程——获取锁——唤醒一个孩子进程——释放锁。
ChildItinerary()思路:
先获得锁conditionLock.acquire()并处于等待状态childOnOahu.sleep(); 判断是否为最后一次运输若是最后一次,直接进行运输并结束。
若不是,则判断是否在Oahu。若在,O岛孩子数减一。判断是否有驾驶员,若有,直接进行运输ChildRideToMolokai();若没有驾驶员,他将成为驾驶员并唤醒一个孩子线程。
若孩子在M岛,则将船开回O岛,O岛孩子数量改变。如果O岛孩子数为1则唤醒大人线程,准备运送大人,否则唤醒孩子线程,继续运送孩子。
AdultItinerary()思路:与ChildItinerary思想类似,但少了返回O岛这一步。大体为获得锁 conditionLock.acquire()并进入等待状态adultOnOahu.sleep(),直接去M岛然后唤醒M岛一个孩子。
本题需要注意的是孩子的运行,因为大人不需要返回O岛,而孩子需要有返回的情况,并且要判断运送孩子时船的状态,为空,半满或者满。
4.6.3 实现代码
public class Boat
{
static BoatGrader bg;
//private static KThread parentThread;
static int childrenOnOahu;
static int adultsOnOahu;
static boolean boatOnOahu;
static int boatState;
static final int boatEmpty=0,boatHalf=1,boatFull=2;
static Lock commonLock;
static Condition2 sleepAdultsOahu,sleepAdultsMolokai;
static Condition2 sleepChildrenOahu,sleepChildrenMolokai;
static Semaphore finished;
public static void selfTest()
{
BoatGrader b = new BoatGrader();
//System.out.println("\n ***Testing Boats with only 2 children***");
//begin(0, 2, b);
System.out.println("\n ***Testing Boats with 5 adult,2 children***");
begin(5, 2, b);
//System.out.println("\n ***Testing Boats with 3 children, 3 adults***");
//begin(3, 3, b);
}
public static void begin(int adults, int children, BoatGrader b) {
//初始情况,o岛上为总人数,船在o岛
Lib.assertTrue(children>=2);
Lib.assertTrue(b!=null);
bg = b;//类变量,存储孩子的最终情况
//parentThread = KThread.currentThread();
childrenOnOahu = children;//
adultsOnOahu = adults;//
boatState=boatEmpty;
boatOnOahu=true;
commonLock = new Lock();//
//static Condition2 sleepAdultsOahu,sleepAdultsMolokai;
//static Condition2 sleepChildrenOahu,sleepChildrenMolokai;
sleepAdultsOahu = new Condition2(commonLock);
sleepAdultsMolokai = new Condition2(commonLock);
sleepChildrenOahu = new Condition2(commonLock);
sleepChildrenMolokai = new Condition2(commonLock);
finished=new Semaphore(0);
for (int i = 0; i < adults; i++)
{
Runnable r = new Runnable(){
public void run() {
AdultItinerary();
}
};
KThread t = new KThread(r);
t.setName("adult"+i+"Thread");
t.fork();
}
for (int i = 0; i < children; i++){
//
Runnable r =new Runnable(){
public void run() {
ChildItinerary();
}
};
KThread t = new KThread(r);
t.setName("child"+i+"Thread");
t.fork();
}
finished.P();
System.out.println("boat test end!");
}
static void AdultItinerary()//
{
boolean onOahu = true;
commonLock.acquire();
while(true){
Lib.assertTrue(onOahu);
if(boatState==boatEmpty&&boatOnOahu&&childrenOnOahu<=1){
onOahu=false;
adultsOnOahu--;
boatOnOahu = false;
bg.AdultRowToMolokai();
if(adultsOnOahu==0&&childrenOnOahu==0){
finished.V();
sleepAdultsMolokai.sleep();
}
sleepChildrenMolokai.wakeAll();//唤醒所有在M岛的孩子,准备往返O岛
sleepAdultsMolokai.sleep();
}
else
sleepAdultsOahu.sleep();//先运送O岛的孩子
}
}
static void ChildItinerary()//
{
boolean onOahu = true;
commonLock.acquire();
while(true)
if(onOahu){
if(boatOnOahu && boatState == boatEmpty){
onOahu = false;
childrenOnOahu--;
bg.ChildRowToMolokai();
if(childrenOnOahu>0){
boatState = boatHalf;
sleepChildrenOahu.wakeAll();
}
else{
boatOnOahu = false;
boatState = boatEmpty;
if(adultsOnOahu == 0 && childrenOnOahu == 0){
finished.V();
sleepChildrenMolokai.sleep();
}
sleepChildrenMolokai.wakeAll();
}
sleepChildrenMolokai.sleep();
}
else if(boatOnOahu && boatState == boatHalf){
onOahu = false;
childrenOnOahu--;
bg.ChildRideToMolokai();
boatOnOahu = false;
boatState = boatEmpty;
if(adultsOnOahu == 0 && childrenOnOahu == 0){
finished.V();
sleepChildrenMolokai.sleep();
}
sleepChildrenMolokai.wakeAll();
sleepChildrenMolokai.sleep();
}
else
sleepChildrenOahu.sleep();
}
else{
if(!boatOnOahu){//船不在O岛,且运输未完,则孩子从M岛来O岛
bg.ChildRowToOahu();
childrenOnOahu++;
boatOnOahu=true;
sleepAdultsOahu.wakeAll();
sleepChildrenOahu.wakeAll();
onOahu=true;
sleepChildrenOahu.sleep();
}
else
sleepChildrenMolokai.sleep();
}
}
4.6.4 测试代码及结果
本题不需要特别的测试函数,此处测试的为5个大人3个孩子的情况。