设计模式之生产者消费者模式


❤实现机制

        生产者生产数据到缓冲区中,消费者从缓冲区中取数据。

        如果缓冲区已经满了,则生产者线程阻塞;

        如果缓冲区为空,那么消费者线程阻塞。

为什么要建立缓冲区?为什么不让生产者直接调用消费者的某个方法直接把数据传递过去?

1.并发性

        由于方法调用是阻塞的,在消费者的方法没有返回之前,生产者只好一直等在那边。万一消费者处理数据很慢,生产者一直等就会白白浪费资源,建立缓冲区以后,生产者把数据往缓冲区一丢,就可以再去生产下一个数据,消费者也直接取缓冲区中的数据,不用再等待生产者生产。

2.耦合性

        如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖,两者之间耦合性增强,所以如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。

为什么要设计锁机制?(个人理解)

        缓冲区满时,生产者休眠,直到被消费者取数据时唤醒。缓冲区为空时,消费者休眠,直到被生产者唤醒。

        A线程为生产者放数据,B线程为消费者取数据,缓冲区有一个数据,大小也为1

        生产者和消费者并发执行,A等待B消费数据,此时正好来了个消费者B2把这一个数据取走,而B去取的时候缓冲区已经为空,B也进行等待A生产,AB互相等待发生死锁。

        所以为了避免多线程死锁现象,就不能让读写数据的方法同时执行。可以用synchronized修饰两个普通方法,因为如果有多个普通方法被synchronized修饰,如果多线程共用一个对象,它们是不会同时执行这些方法的。如果是不同对象,他们会同时执行自己的方法

具体实现

        以下三种方法中Resource是缓冲区,所有生产者消费者线程都共用resource这个对象

1.方法一

        通过Object类的wait()notify()notifyAll()方法配合synchronized关键字实现线程的等待/通知)

Resource类(缓冲区)

public class Resource {
	private int num = 0;// 当前资源数量
	private int size = 10;// 资源池中允许存放的资源数目

	public synchronized void remove() {
		if (num > 0) {
			num--;
			System.out.println("消费者:" + Thread.currentThread().getName()
					+ "消耗一件资源," + "当前缓冲区有" + num + "个");
			notifyAll();// 通知生产者生产资源
		} else {
			try {
				System.out.println("消费者:" + Thread.currentThread().getName() + "线程等待");
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public synchronized void add() {
		if (num < size) {
			num++;
			System.out.println("生产者:" + Thread.currentThread().getName()
					+ "生产一件资源,当前缓冲区有" + num + "个");
			// 通知等待的消费者
			notifyAll();
		} else {
			// 如果当前资源池中有10件资源
			try {
				System.out.println("生产者:" + Thread.currentThread().getName() + "线程等待");
				wait();// 生产者进入等待状态,并释放锁
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

ProducerThread(生产者)

public class ProducerThread extends Thread{
    private Resource resource;
    public ProducerThread(Resource resource){
        this.resource = resource;
    }
@Override
    public void run() {
        //不断地生产资源
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            resource.add();
        }
    } 	
}

ConsumerThread(消费者)

public class ConsumerThread extends Thread {
	private Resource resource;
	public ConsumerThread(Resource resource) {
		this.resource = resource;
	}
	@Override
	public void run() {
		while (true) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			resource.remove();
		}
	}
}

Client(测试类)

public class Client {
    public static void main(String[] args) {
        Resource resource = new Resource();
        //生产者线程
        ProducerThread p1 = new ProducerThread(resource);
        ProducerThread p2 = new ProducerThread(resource);
        ProducerThread p3 = new ProducerThread(resource);
        //消费者线程
        ConsumerThread c1 = new ConsumerThread(resource);
        ConsumerThread c2 = new ConsumerThread(resource);
        ConsumerThread c3 = new ConsumerThread(resource);
    
        p1.start();
        p2.start();
        p3.start();
        c1.start();
        c2.start();
//        c3.start();
    }
}

        方法一其实相当于是一个队列,要么放数据要么取数据,两个操作不会同时执行,效率低。

2.方法二

        通过Condition接口的await()、signal()、signalAll()配合Lock锁实现线程的等待/通知机制。

使用注意

1.Condition接口依赖于Lock接口,通过lock.newCondition()生成一个Condition并绑定到lock锁上 

2.Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用

相比Object的那一套具有的特性:

1.Condition接口可以支持多个等待队列,因为一个Lock实例可以绑定多个Condition,而消费者和生产者都有各自的Condition,从而用户可以只在指定的等待队列上唤醒线程,而不是唤醒所有的线程。

2.Condition接口支持等待中断

3.Condition接口支持定时等待

Resource

public class Resource {
	private int num = 0;// 当前资源数量
	private int size = 10;// 资源池中允许存放的资源数目
	private Lock lock;
	private Condition producerCondition;
	private Condition consumerCondition;

	public Resource(Lock lock, Condition producerCondition,
			Condition consumerCondition) {
		this.lock = lock;
		this.producerCondition = producerCondition;
		this.consumerCondition = consumerCondition;
	}
	public void add() {
		lock.lock();
		try {
			if (num < size) {
				num++;
				System.out.println(Thread.currentThread().getName()
						+ "生产一件资源,当前资源池有" + num + "个");
				consumerCondition.signalAll();
			} else {
				try {
					producerCondition.await();
					System.out.println(Thread.currentThread().getName()
							+ "线程进入等待");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		} finally {
			lock.unlock();
		}
	}

	public void remove() {
		lock.lock();
		try {
			if (num > 0) {
				num--;
				System.out.println("消费者" + Thread.currentThread().getName()
						+ "消耗一件资源," + "当前资源池有" + num + "个");
				producerCondition.signalAll();
			} else {
				try {
					consumerCondition.await();
					System.out.println(Thread.currentThread().getName()
							+ "线程进入等待");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		} finally {
			lock.unlock();
		}
	}
}

ProducerThread

public class ProducerThread extends Thread {
	private Resource resource;
	public ProducerThread(Resource resource) {
		this.resource = resource;
	}
	public void run() {
		while (true) {
			//为了便于观察增加延迟执行
			try {
				Thread.sleep((long) (1000 * Math.random()));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			resource.add();
		}
	}
}

ConsumerThread

public class ConsumerThread extends Thread {
	private Resource resource;
	public ConsumerThread(Resource resource) {
		this.resource = resource;
	}
	public void run() {
		while (true) {
			try {
				Thread.sleep((long) (1000 * Math.random()));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			resource.remove();
		}
	}
}

Client

public class Client {
	public static void main(String[] args) {
		// 1.创建一个可重入锁
		Lock lock = new ReentrantLock();
		// 2.获取Condition实例
		Condition producerCondition = lock.newCondition();// 获取绑定到lock锁上的Condition的一个实例
		Condition consumerCondition = lock.newCondition();
		Resource resource = new Resource(lock, producerCondition, consumerCondition);
		// 生产者线程
		ProducerThread producer1 = new ProducerThread(resource);
		ProducerThread producer2 = new ProducerThread(resource);
		ProducerThread producer3 = new ProducerThread(resource);
		// 消费者线程
		ConsumerThread consumer1 = new ConsumerThread(resource);
		ConsumerThread consumer2 = new ConsumerThread(resource);
		ConsumerThread consumer3 = new ConsumerThread(resource);
		producer1.start();
		producer2.start();
		producer3.start();
		consumer1.start();
		consumer2.start();
		consumer3.start();
	}
}

3.方法三

        通过阻塞队列BlockingQueue和它的两个方法put()、take()方法实现

        put():队列未满时,直接插入没有返回值;队列满时会阻塞等待,一直等到队列未满时再插入。 

        take():队列不为空返回队首值并移除;当队列为空时会阻塞等待,一直等到队列不为空时再返回队首值

Resource

public class Resource {
	private BlockingQueue resourceQueue = new LinkedBlockingQueue(10);
    public void add(){
        try {
            resourceQueue.put(1);// 这里在队尾放入一个数字1
            System.out.println("生产者" + Thread.currentThread().getName() + "生产一件资源," + "当前资源池有" + resourceQueue.size() + "个资源");// 注意这里的个数是队列的大小
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void remove(){
        try {
            resourceQueue.take();
            System.out.println("消费者" + Thread.currentThread().getName() + "消耗一件资源," + "当前资源池有" + resourceQueue.size() + "个资源");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ProducerThread

public class ProducerThread extends Thread {
	private Resource resource;
	public ProducerThread(Resource resource) {
		this.resource = resource;
	}
	public void run() {
		while (true) {
			//为了便于观察增加延迟执行
			try {
				Thread.sleep((long) (1000 * Math.random()));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			resource.add();
		}
	}
}

ConsumerThread

public class ConsumerThread extends Thread {
	private Resource resource;
	public ConsumerThread(Resource resource) {
		this.resource = resource;
	}
	public void run() {
		while (true) {
			try {
				Thread.sleep((long) (1000 * Math.random()));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			resource.remove();
		}
	}
}

Client

public class Client {
	public static void main(String[] args) {
		Resource resource = new Resource();
		// 生产者线程
		ProducerThread producer1 = new ProducerThread(resource);
		ProducerThread producer2 = new ProducerThread(resource);
		ProducerThread producer3 = new ProducerThread(resource);
		// 消费者线程
		ConsumerThread consumer1 = new ConsumerThread(resource);
		ConsumerThread consumer2 = new ConsumerThread(resource);
		ConsumerThread consumer3 = new ConsumerThread(resource);
		producer1.start();
		producer2.start();
		producer3.start();
		consumer1.start();
		consumer2.start();
		consumer3.start();
	}
}











评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值