生产者和消费者问题是一个著名的进程同步问题
什么是进程同步?
多个进程在执行的时候,它们要按照一定的规则共享系统资源,这种进程间的相互制约关系就是进程同步机制。
问题描述:
生产者进程:生产产品
消费者进程:消费产品
两者之间设置了个缓冲池(含有n个缓冲区)
生产者生产完,把产品放进一个缓冲区里,消费者消费,就从一个缓冲区中取走产品
两个进程必须同步:不允许消费者进程从一个空缓冲区中取产品,也不允许生产者进程向一个已经满了的缓冲区投放产品
分析:
我们定义一些变量:
缓冲池:buffer[n]
投入一个产品:数组单元指针in加1,表示成in=(in+1)%n,含义是暂存产品数量加1
取走一个产品:数组单元指针out加1,表示成out=(out+1)%n,含义是空闲单元数量加1
对n取余是因为缓冲池里的缓冲区是循环组成的。
变量counter:投放产品:counter+1,取走产品:counter-1
初始化:
int in=0,out=0,counter=0;
item buffer[n]
以上变量生产者进程和消费者进程共有
用生产者进程的局部变量nextp暂时存放刚刚生产出来的产品。
用消费者进程的局部变量nextc暂时存放每次要消费的产品。
void producer(){
while(1){
produce an item in nextp;
...
while(counter==n)
;
buffer[in]=nextp;
in=(in+1)%n;
counter++;
}
};
void consumer(){
while(1){
while(counter==0)
;
nextc=buffer[out];
out=(out+1)%n;
counter--;
consume the item in nextc;
...
}
};
但是这两个程序在并发执行会出现错误,问题在于这两个进程共享变量counter
counter的加1和减1操作我们这样描述:
register1=counter; register2=counter;
register1=register1+1; register2=register2-1;
counter=register1; counter=register2;
这六个语句的执行顺序如果发生改变,得到的最后的counter的值可能不同。
解决:把变量counter作为临界资源,生产者进程和消费者进程互斥地访问 counter
用java写个生产者和消费者模型
实际上需要一个缓冲区,但是本例只设了一个共享整数
package thread_test;
//消费者
class Consumer extends Thread {
private ShareArea sharedObject;
public Consumer(ShareArea shared) {
sharedObject = shared;
}
public void run() {
int value;
do {
try {
Thread.sleep((int)(Math.random()*3000));
}catch(InterruptedException exception){}
value = sharedObject.getSharedInt(); //获取共享整数的值
System.out.println("消费:"+ value);
}while(value != 10);
}
}
//生产者
class Producer extends Thread {
private ShareArea sharedObject;
public Producer(ShareArea shared) {
sharedObject = shared;
}
public void run() {
for(int cnt = 1;cnt <=10 ;cnt++) {
try {
Thread.sleep((int)(Math.random()*2000));
}catch(InterruptedException exception){}
sharedObject.setSharedInt(cnt);//更改共享数据
System.out.println("生产:" + cnt);
}
}
}
//共享数据访问程序
class ShareArea {
private int sharedInt = -1; //共享整数
private boolean writable = true;//条件变量
public synchronized void setSharedInt(int value) {
while(! writable) {
try {
wait(); //轮不到生产者写就等待
}catch(InterruptedException exception) {}
}
sharedInt = value; //生产者写入一个值
writable = false; //消费者操作前,生产者不能写入另一个值
notify(); //唤醒等待资源的线程
}
public synchronized int getSharedInt() {
while(writable) {
try {
wait(); //没轮到消费者就等待
}catch(InterruptedException exception) {}
}
writable = true; //生产者要等生产者再生产才能消费另一个值
notify(); //唤醒等待资源的线程
return sharedInt; //消费者得到数据
}
}
public class SharedTest {
public static void main(String args[]) {
ShareArea shareObject = new ShareArea();
Producer p =new Producer(shareObject);
Consumer c =new Consumer(shareObject);
p.start();c.start();
}
}
上面提到的synchronized关键字是一种锁
java中,谈到线程安全问题,常见操作就是加锁来实现资源共享,一般来说主要有synchronize和Lock两种锁
一、锁的概念
当有多个线程想要访问一临界资源的时候,就会出现冲突,而锁就是解决这种冲突的。和上厕所一样,假如有ABC三个人都来上厕所,A先来了,那么在A出来之前,这个厕所就处在了“锁”定状态,B和C憋死也要在外面等着。
二、synchronized关键字的使用
- 修饰代码块,锁的是当前代码块
public void show() {
Object o=new Object();
synchronized (o) {
System.out.println("Synchronized修饰的代码块!");
}
}
- 修饰一般方法,锁的是实例对象
public synchronized void show() {
System.out.println("Synchronized修饰的一般方法!");
}
public static void main(String[] args) {
Test test=new Test();
test.show();
}
- 修饰静态方法,锁的是类对象
public static synchronized void show() {
System.out.println("Synchronized修饰的静态方法!");
}
wait():释放占有的对象锁,释放CPU
notify():唤醒等待队列中的一个线程,使其获得锁进行访问