最近遇到一个面试题,说是模拟生产者消费者问题并且不能使用concurrent包,思路是使用信号量Semaphore和PV操作,代码如下
/**
* Created by violetMoon on 2016/5/12.
*/
public class ConsumerTest {
static class Semaphore {
private Object lock = new Object();
private int value;
public Semaphore(int value) {
this.value = value;
}
public void p() {
synchronized (lock) {
value--;
if (value < 0)
try {
System.out.println(Thread.currentThread().getName() + " go to sleep");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void v() {
synchronized (lock) {
value++;
if (value <= 0) {
System.out.println(Thread.currentThread().getName() + " waitup a thread");
lock.notify();
}
}
}
}
static int in;
static int out;
public void test() {
final int BUFFER_SIZE = 5;
Integer[] breads = new Integer[BUFFER_SIZE];
in = 0;
out = 0;
Semaphore putSema = new Semaphore(BUFFER_SIZE); //一开始缓冲区为空,所以可以填充的数量为缓冲区大小
Semaphore getSema = new Semaphore(0); //一开始没有面包
Semaphore putMutex = new Semaphore(1);
Semaphore getMutex = new Semaphore(1);
Runnable consumer = new Runnable() {
@Override
public void run() {
while (true) {
Integer myBread = null;
getSema.p();
getMutex.p();
myBread = breads[out];
out = (out + 1) % BUFFER_SIZE;
System.out.println(Thread.currentThread().getName() + " eat bread:" + myBread);
getMutex.v();
putSema.v();
}
}
};
Runnable worker = new Runnable() {
@Override
public void run() {
while (true) {
Integer newBread = null;
putSema.p();
putMutex.p();
newBread = new Integer(in);
breads[in] = newBread;
System.out.println(Thread.currentThread().getName() + " produce bread:" + newBread);
in = (in + 1) % BUFFER_SIZE;
putMutex.v();
getSema.v();
}
}
};
int consumerNum = 4;
for (int i=0; i<consumerNum; ++i)
new Thread(consumer, "consumer" + (i + 1)).start();
int workerNum = 4;
for (int i=0; i<workerNum; ++i)
new Thread(worker, "worker" + (i + 1)).start();
}
public static void main(String[] args) {
new ConsumerTest().test();
}
}
Semaphore的p操作相当于获取可用资源,使用lock锁来实现value操作的原子性,以及在没有可用资源时调用lock.wait()释放当前锁并进入等待,v操作相当于释放资源,value<0表明有线程在lock上等待资源,所以调用notify释放其中一个线程
为了方便说明把putSema的资源称作写许可,把getSema的资源称作读许可。
生产者先调用putSema.p()来获取一个写许可,如果没有陷入等待,之后使用putMutex来实现生产者写互斥,进入临界区后将面包放到缓冲区里,使用putMutex.v()释放互斥锁,之后调用getSema.v()释放一个读许可。
消费者先调用getSema.p()来获取一个读许可,如果没有陷入等待,之后使用getMutex来实现消费者读互斥,进入临界区后吃掉一个面包,使用getMutex.v()释放互斥锁,之后调用putSema.p()释放一个写许可