2011/5/28操作系统学习笔记之经典同步问题
进程同步之经典同步问题
1.有限缓存问题
生产者通过调用insert()函数,将一个数据项放入缓冲区;消费者通过调用remove()移出数据项。
public class BoundedBuffer {
private static final int BUFFER_SIZE = 5; //缓冲区的容量
private Object[] buffer;
private int in,out;
private Semaphore mutex; //提供了对缓冲区访问的互斥要求,初始化为1
private Semaphore empty; //表示空缓冲项的个数,初始化为缓冲区的容量
private Semaphore full; //表示满缓存项的个数,初始化为0
public BoundedBuffer(){
in = 0;
out = 0;
buffer = new Object[BUFFER_SIZE];
mutex = new Semaphore(1);
empty = new Semaphore(BUFFER_SIZE);
full = new Semaphore(0);
}
/*
* 生产者将数据项放入缓冲区
*/
public void insert() throws InterruptedException{
empty.acquire();
mutex.acquire();
Object item = "产品1";
buffer[in] = item; //在缓存(buffer)里增加一个产品(item)
in = (in + 1)%BUFFER_SIZE;
mutex.release();
empty.release();
}
/*
* 消费者移出数据项
*/
public Object remove() throws InterruptedException{
full.acquire();
mutex.acquire();
Object item = buffer[out]; //从缓存中移出产品
out = (out+1)%BUFFER_SIZE;
mutex.release();
full.release();
return item;
}
}
或者采用
public synchronized void insert(Object item){
while(count == BUFFER_SIZE){
try{
wait();
}catch(InterruptedException e){}
}
++count;
buffer[i] = item;
in = (in + 1)%BUFFER_SIZE;
notify();
}
public synchronized Object remove(){
Object item;
while(count == 0){
try{
wait();
}catch(InterruptedException e){}
}
--count;
item = buffer[out];
out = (out + 1)%BUFFER_SIZE;
notify();
return item;
}
2.读-写问题
一个数据库需要为多个并发线程所共享。其中,有的线程可能只需要读数据库(读者),而其他线程可能要更新(读和写)数据库(写者)。显然,如果这些线程同时访问共享对象,可能会产生混乱。
为了确保不会产生这样的困难,要求写者对共享数据库有排他的访问。最为简单的是:第一读者-写者问题(写者可能会饥饿)、第一写者-读者问题(读者可能会饥饿)。
读写锁在以下情况最有用:
1.当可以容易地区分哪些线程只需要读共享数据而哪些线程只需要写共享数据时。
2.当读线程数比写线程数多时。(因为读写锁的建立开销比信号量或互斥锁大,而这一开销可以通过允许多个读者并发数的增加来加以弥补)
解决方案:当写者希望开始写时,它首先检查当前数据库是否正在被读或写。如果数据库正在被读或写,写者进入对象等待集合。否则设置dbWriting为true。写者完成时,设置dbWriting为false。
读者调用acquireReadLock()时,它会检查当前数据库是否正在被写。如果数据库不可用,读者进入对象等待集合;否则,它增加readerCount。最后一个读者调用releaseReadLock(),调用notify(),由此通知一个等待的写者。然后写者调用releaseWriteLock()时,它调用notifyAll()方法而不是notify()。
考虑对读者的影响,如果几个读者希望读数据库,而数据库正在被写,一旦写数据库完成,假设写者调用notify(),那么只有一个读者接收到通知。此时,即时数据库可被读,其他读者仍停留在等待集合中。通过调用notifyAll(),离开的写者确保可通知所有等待的读者。
3.哲学家进餐问题
需要在多个进程之间分配多个资源且不会出现死锁和饥饿的典型例子:假设有5个哲学家,他们把医生都拿来思考和吃饭。他们共用一个圆桌吃饭,有一锅米饭,每个人两边有两根筷子。当某位哲学家思考时,他与其他同事不交互。时而,他会感到饥饿,并试图拿起与他相近的左右两根筷子。当5个哲学家同时饥饿,且同时那起一根的筷子,他们会永远等待,陷入死锁。就算没有发生死锁,也会发生“资源耗尽”。
有很多解决死锁问题的方法:
一、最多只允许四个哲学家同时坐在桌子上;
二、只有两只筷子都可用时才允许一个哲学家拿起它们(他必须在临界区内拿起两根筷子);
三、使用非堆成解决方法。例如:奇数哲学家先拿起左边的筷子,接着拿起右边的筷子,而偶数的哲学家先拿起右边的筷子,接着拿起左边的筷子。
管程的解决方案:
区分哲学家所处的三这个状态:enum State{THINKING,HUNGRY,EATING};
State states = new State[5];第i个哲学家只有在其两个邻居不进餐时才能将变量state[i]设置为State.EATING:(state[(i+4)%5]!=State.EATING)和(state[(i+1)%5]!=State.EATING)
还需要声明,其中哲学家i在饥饿且又不能拿到所需的筷子时刻延迟自己:Condition[] self = new Condition[5];
筷子的分布由管程dp来控制,管程dp是监视器(Monitor)类型DiningPhilosophers的实例。每个哲学家用餐之前,必须调用takeForks()操作,挂起该哲学家线程。在成功完成该操作之后,该哲学家才可进餐。接着,他可调用returnForks()操作,并开始思考。
monitor DiningPhilosophers{
public DiningPhilosophers{
for(int i = 0;i<5;i++)
state[i]=state.THINKING;
}
public void takeForks(int i){
state[i] = State.HUNGRY;
test(i);
if(state[i]!=state.EATING)
self[i].wait();
}
public void returnForks(int i){
state[i]=state.THINKING;
test((i+4)%5);
test((i+1)%5);
}
private void test(int i){
if((state[(i+4)%5]!=state.EATING) &&
(state[i] = State.HUNGRY) &&
(state[(i+1)%5]!=state.EATING)){
state[i] = State.EATING;
self[i].signal();
}
}
}