Java_生产者消费者模型

一、Semaphore类概述

  • Semaphore是一个计数信号量。 在概念上,信号量维持一组许可证,可以用来控制同时访问特定资源的线程数量(即,不再是共享资源一次只能被一个线程访问)。其构造方法允许我们一开始先设定许可集的数量。
  • 信号量通常用于限制线程数,而不是访问某些(物理或逻辑)资源。
  • 使用信号量来控制对一个项目池的访问时,在获得项目之前,每个线程必须从信号量获取许可证,以确保某个项目可用。 当线程完成该项目后,它将返回到池中,并将许可证返回到信号量,允许另一个线程获取该项目。
  • 若信号量被初始化为一个,即Semaphore mutex = new Semaphore(1);,则使得它只有至多一个允许可用,从而可以用作互斥锁。 这通常被称为二进制信号量 ,因为它只有两个状态:一个许可证可用,或零个许可证可用。

(PS. 以上是在jdk手册中看到的一些说明,实际上我还是不太懂,它到底有什么用、与线程之间又有什么关系、所谓信号量到底是怎么个意思怎么个用途。于是,有了下面的搜索结果。)

  • Semaphore负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。Semaphore也是操作系统中用于控制进程同步互斥的量。
  • Semaphore分为单值和多值两种,前者只能被一个线程获得,后者可以被若干个线程获得。
  • 下面这个例子更有利于得到上面的问题的答案:

以一个停车场的运作为例。为简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆不受阻碍的进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入一辆,如果又离开两辆,则又可以放入两辆,如此往复。

在这个 停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是 信号量的作用。

更进一步, 信号量的特性如下:信号量是一个非负整数(车位数),所有通过它的线程(车辆)都会将该整数减一(通过它当然是为了使用资源),当该整数值为零时,所有试图通过它的线程都将处于等待状态。在 信号量上我们定义两种操作: Wait(等待) 和 Release(释放)。 当一个线程调用Wait(等待)操作时,它要么通过然后将 信号量减一,要么一直等下去,直到信号量大于一或超时。Release(释放)实际上是在 信号量上执行加操作,对应于车辆离开停车场,该操作之所以叫做“释放”是因为加操作实际上是释放了由信号量守护的资源

这里用到的Semaphore类中的两个方法的介绍:

(PS.这两个方法并非很简单,较为具体的介绍可点击这里

  • acquire(n):表示某个线程得到了执行的许可。只要调用了一次acquire(),就会将这个信号量的许可集的数量permits - n,表示此后还可以有多少个线程来使用与这一信号量相关联的资源。
  • release(n):释放阻塞,相当于在许可集上添加了一个许可。即,将原来被占用的位置返回给许可集,告诉它这个位置现在是可以用的了。只要调用一次release(),就会使信号量的许可集的数量permits + n。

二、生产者于消费者模型

  • 对于生产者来说:生产出产品后,能够将产品放入仓库的前提是仓库未储满。
  • 对于消费者来说:从仓库中得到产品的前提是仓库中储有货物。
  • 生产者与消费者是线程的体现。

三、代码实现讲解

1、三个Semaphore对象:

Semaphore mutex = new Semaphore(1);
//表示互斥信号量,只能有一个,用来访问共享资源(访问共享资源时,一次只能有一个线程访问,用此来判断这个资源是否已经被占用)。
Semaphore full_num = new Semaphore(n); 
//用来表示仓库的容量,设定为n。
Semaphore prod_num = new Semaphore(0);
//用来表示当前仓库中的产品数量,初始为0。

2、(生产者)产品放入仓库——push()

  1. 获取临界资源:mutex.acquire(1);
    mutex是一个互斥信号量,保证每次的执行只能能是某一个生产者(线程)或某一个消费者(线程)。如何保证的呢?因为创建时的参数是1,所以只要一被调用,就变成0。底层会判断这个信号量当前是不是0,不是的话就可以占用此临界资源,否则,表示该临界资源正在被某个线程占用呢。
  2. 生产产品放入库中,并使库中还可存放的产品总数-1:full_num.acquire(1);
    此时full_num - 1,这里表示此后还能容纳的产品数,这里也就直接相当于该信号量还能允许执行多少次producer线程(每执行一次线程就生产出一个产品)。如果此时仓库已满,就让该线程处于休眠状态,以便期望消费者线程来消费产品,从而释放仓库的空间。
  3. push()动作执行
  4. 释放信号量:mutex.release(); prod_num.release();
    这里着实让我感受到了Semaphore使用的灵活性。根据实际情况,也就是我们赋予的Semaphore对象的意义,full_num就不需要进行release()!而mutex进行release()是为了其他生产者或消费者线程执行相应的操作;prod_num进行release()是因为执行了一次生产产品的操作(即步骤2),那么产品数量prod_num+1!是的,可以随时使用release()方法使信号量的许可数量随时增加

3、(消费者)产品从仓库中拿出——pop()

  1. 获取临界资源:mutex.acquire(1);
  2. 消费者取得一个产品,仓库中产品总数prod_num - 1:prod_num.acquire();
    如果此时还没有产品,就让此线程进入休眠状态,期望生产者线程进行生产操作。
  3. pop()动作执行
  4. 释放信号量:mutex.release(); full_num.release();
    相关解释不再赘述。

四、代码

/*
 * SemStack是实例中的栈类(为了与核心java.util.Stack相区别,我们将此命名为SemStack)。
 * 在栈中对不同意义的信号量进行了初始化。
 * 使用信号量Semaphore就不用保证pop()、push()为synchronized了。
 * */
public class SemStack {
	private static final int MAX_CONTENT_NUM = 50;
	private Semaphore mutex = new Semaphore(1);
	private Semaphore empty_num = new Semaphore(MAX_CONTENT_NUM);//空位的数量
	private Semaphore prod_num = new Semaphore(0);
	private List<Character> buffer = new ArrayList<Character>();
	
	//产品从仓库中取出
	public char pop() {
		char c;		
							
		try {	
			//下面的两个对象的acquire()有逻辑顺序问题。只有保证仓库中数量为空可以拿到产品的情况下,再对互斥信号量上锁,否则会出现“死锁”的现象。push()也需注意。
			prod_num.acquire(1);
			mutex.acquire(1);			
		} catch (InterruptedException e) {
			System.out.println("[仓库中产品数量为空,无法获得产品] 或 [互斥信号量被占用]");
			e.printStackTrace();
		}
		
		c = ((Character) buffer.get(buffer.size() - 1)).charValue();//保存仓库中的最后一个产品,用来返回;
		buffer.remove(buffer.size() - 1);	//移出仓库中的最后一个产品(最后一个产品是库中所有产品中进去最晚的,从而实现了栈机制——先进后出FILO)
		empty_num.release(1);
		mutex.release(1);
		return c;
	}
	
	//向仓库中增加产品
	public void push(char c) {		
		Character charObj = new Character(c);
		
		try {
			empty_num.acquire(1);
			mutex.acquire(1);
			buffer.add(charObj);	//向仓库中添加一个产品	
		} catch (InterruptedException e) {
			System.out.println("[仓库中产品数量已满,无法增加产品] 或 [互斥信号量被占用]  ");
			e.printStackTrace();
		} finally {		
			prod_num.release(1);
			mutex.release(1);
		}	
	}
}

/*
 * 生产者线程的run()需要执行push()操作;
 * push()操作里通过对信号量的控制实现指定数量线程的启动。 
 * */
public class Producer implements Runnable{
	private SemStack stack;
	private int num;
	private static int count = 1;
	
	public Producer(SemStack stack) {
		this.stack = stack;
		this.num = count++;
	}
	
	@Override
	public void run() {
		char c;
		for(int i = 0; i < 6; ) {		
			c = (char)(Math.random() * 26 + 'A');
			stack.push(c);
			System.out.println("Producer" + num + ": " + c);
			try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
		}
	}	
}
/*
 * 消费者线程的run()需要执行pop()操作;
 * pop()操作里通过对信号量的控制实现指定数量线程的启动。 
 * */
public class Consumer implements Runnable{
	private SemStack stack = new SemStack();
	private int num;
	private static int counter = 1;
	
	public Consumer(SemStack stack) {
		this.stack = stack;
		this.num = counter++;
	}
	
	@Override
	public void run() {		
		char c;
		for (int i = 0; i < 6; ) {
			c = stack.pop();
			System.out.println("Consumer" + num + ": " + c);	
			try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
		}	
	}
}
public class Manager {
	/**
	 * @author zjy/2019.4.20
	 * 主测试程序
	 */
	public static void main(String[] args) {
		//创建栈
		SemStack stack = new SemStack();
		//创建生产者线程
		Producer pro[] = new Producer[20];
		Thread proThread[] = new Thread[20];
		for (int i = 0; i < 20; i++) {
			pro[i] = new Producer(stack);
			proThread[i] = new Thread(pro[i]);
		}
		//创建消费者线程
		Consumer con[] = new Consumer[20];
		Thread conThread[] = new Thread[20];
		for (int i = 0; i < 20; i++) {
			con[i] = new Consumer(stack);
			conThread[i] = new Thread(con[i]);
		}
		//开启线程
		for (int i = 0; i < 20; i++) {
			proThread[i].start();
		}
		for (int i = 0; i < 20; i++) {
			conThread[i].start();
		}
	}
}

下面是使用notify()等方法创建的同步栈,不知道对不对还。。。先保留在此

package producer_consumer;

import java.util.ArrayList;
import java.util.List;

public class SynStack0 {
	private List buffer = new ArrayList();
	
	public synchronized char pop() {
		char c;
		while (buffer.size() == 0) {
			System.out.println(Thread.currentThread().getName() + " now wait=============");
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}	
		c = ((Character) buffer.get(buffer.size() - 1)).charValue();
		buffer.remove(buffer.size() - 1);
		return c;
		
	}
	
	public synchronized void push(char c) {
		this.notify();
		Character charObj = new Character(c);
		buffer.add(charObj);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值