在两种情况下需要同步,一是访问临届资源,二是线程或进程间合作。这两种对应Java语言的API分别是锁跟wait/notify。同步进入临界区通常需要满足4个条件:
1. 空闲让进,这是显然的
2. 忙则等待,这也是
显然的
3. 有限等待,这是防止线程死等,synchronized并不支持,并发包中的Lock支持
4. 让权等待,不能进入临界区时,立即释放处理机。这条可不一定要遵循,现在都有多个处理机,让线程自旋会,可能有更好的性能。
一. 自旋PV
P(S): V(S):
while(S<=0); S=S+1;
S=S-1;
这个显然不支持
让权等待,但不见的就是不好的。
二. 阻塞型PV
P(S): V(S):
S=S-1; S=S+1;
if(S<0) block(S, thread); if(S<=0) wakeup(S);
这符合让权等待,类似于Java中的wait/notify。
三. 生产者消费者问题
这是个经典问题,并发编程很多地方需要用到该模式,能使程序更加清晰。
这里的临界资源是缓冲区,缓冲区必有个属性,那就是大小(n),还有个属性就是当前容量(in)。其中n是不变量,in是个变量随生成跟消费而变动,所以in肯定需要互斥访问,则需要一个信号量mutex=1来保证in的互斥访问。生产者能生产的条件是缓冲区还有空间,可用一个信号量empty=n来表示是否还有空间。消费者能消费的条件是缓冲区有元素可以消费,可用信号量full=0来表示。
所以:需要第一3个信号量,mutex=1、empty=n、full=0。
生产者: 消费者:
item = produce(); P(full)
P(empty) P(mutex)
P(mutex) item=buffer(in);
buffer(in) = item; in=in-1;
in = (in + 1)%n; V(mutex);
V(mutex) V(empty);
V(full)
-
static class BlockQ<E> {
-
private int n;
-
private int size = 0;
-
private Object[] ar;
-
-
public BlockQ(int n) {
-
this.n = n;
-
ar = new Object[n];
-
}
-
-
public synchronized void put(E e) throws InterruptedException {
-
-
while (size >= n)
-
wait();
-
ar[size++] = e;
-
notifyAll();
-
-
}
-
-
@SuppressWarnings("unchecked")
-
public synchronized E take() throws InterruptedException {
-
E e = null;
-
-
while (size <= 0)
-
wait();
-
e = (E) ar[--size];
-
notifyAll();
-
-
return e;
-
}
-
}
-
-
static class BlockQ2<E> {
-
private Semaphore empty;
-
private Semaphore mutex;
-
private Semaphore full;
-
private int in = 0;
-
private Object[] ar;
-
-
public BlockQ2(int n) {
-
empty = new Semaphore(n);
-
full = new Semaphore(0);
-
mutex = new Semaphore(1);
-
ar = new Object[n];
-
}
-
-
public void put(E e) throws InterruptedException {
-
empty.acquire();
-
mutex.acquire();
-
ar[in++] = e;
-
mutex.release();
-
full.release();
-
}
-
-
@SuppressWarnings("unchecked")
-
public E take() throws InterruptedException {
-
E e = null;
-
full.acquire();
-
mutex.acquire();
-
e = (E) ar[--in];
-
mutex.release();
-
empty.release();
-
return e;
-
}
-
}
阅读(906) | 评论(0) | 转发(0) |