生产者和消费者之间的阻塞队列
生产者消费者模型在实际生活中很多运用。对我们自己来说就是一个消费者,比如我们需要买奥利奥饼干,我们会去超时买,并不是直接从厂商那里买,而厂商把奥利奥生产结束后会送往各个超市进行售卖,超市就是我们消费者和生产者之间媒介。这个超市在操作系统定义为阻塞队列。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从
阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
在了解生产者消费者模型之前需要先了解几个方法~~
wait()方法
public final void wait() throws InterruptedException
wait()使线程停止运行,会释放对象锁。
- wait()方法必须用于同步代码块和同步方法,而且必须是内建锁(synchronized),因为需要锁住的对象。如果调用wait()时没有合适的锁,会抛异常。
- wait()方法会使当前线程调用该方法后进行等待,并且将该线程置入锁对象的等待队列中,直到接到通知或被中断而已。如果没有接到通知或者中断,会一直等。
- wait()方法执行后当前线程释放锁,其他线程可以竞争该锁。
package CODE.多线程;
public class Wait1
{
public static void main(String[] args) throws InterruptedException {
Object ob=new Object();
synchronized (ob)
{
System.out.println("等待开始...");
ob.wait();
System.out.println("等待结束...");
}
System.out.println("main方法结束...");
}
}
只会打印出“等待开始…”,因为没有唤醒等待,该进程就一直等待…,不会运行后面语句。
wait方法从运行态到阻塞态
wait (long time):如果到了预计时间还没被唤醒,将继续执行后续代码
wait( )之后的线程继续执行有2种方法:
- 调用该对象的notify( )方法唤醒等待线程
- 线程等待时调用interrupt( )中断该线程
1.首先用notify()唤醒线程:
notify()
public final native void notify();
- notify( )方法也必须在同步方法或者同步代码块中调用,用来唤醒等待在该对象上的线程。如果有多个线程等待,则任意挑一个线程唤醒.
- notify( )方法执行后,唤醒线程不会立即释放对象锁,要等待唤醒线程全部执行才会释放锁。
import static java.lang.Thread.sleep;
//notify()
class waitnotifyT
{
synchronized public void waitMethod() {
System.out.println("等待开始...");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("等待结束...");
}
synchronized public void notifyMethod() {
System.out.println("唤醒开始...");
notify();
System.out.println("唤醒结束...");
}
}
class Mthread implements Runnable
{
private waitnotifyT waitnotifyT1;
private int flag=1;
public Mthread(waitnotifyT waitnotifyT1,int flag) {
this.waitnotifyT1 = waitnotifyT1;
this.flag=flag;
}
@Override
public void run()
{
if(flag==1)
{
waitnotifyT1.waitMethod();
}
else
waitnotifyT1.notifyMethod();
}
}
public class Wait1
{
public static void main(String[] args) throws InterruptedException {
waitnotifyT wn=new waitnotifyT();
Mthread waitThread=new Mthread(wn,1);
Mthread notifyThread=new Mthread(wn,0);
new Thread(waitThread).start();
sleep(1000);
new Thread(notifyThread).start();
}
}
从结果可以看见当执行notify后,唤醒线程不会立即释放锁,而是唤醒线程全部执行完,才会释放锁,执行wait()之后的代码。
2.当线程使用wait导致线程阻塞,调用interrupt会抛一个InterruptedException异常,将线程终止。
class WaitTh implements Runnable
{
synchronized private void waitM()
{
System.out.println("等待开始...");
try {
wait();
} catch (InterruptedException e) {
System.out.println("等待被中断"); }
}
public void run()
{
waitM();
}
}
public class Wait1
{
public static void main(String[] args) {
Thread thread=new Thread(new WaitTh());
thread.start();
thread.interrupt();
}
}
notify和wait等待和唤醒的是同一个对象
notifyAll()
notifyAll方法可以一次唤醒所有的等待在该对象上的线程。
public final native void notifyAll();
////notifyAll()
class waitnotifyT
{
synchronized public void waitMethod() {
System.out.println(Thread.currentThread().getName()+":等待开始...");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":等待结束...");
}
synchronized public void notifyMethod() {
System.out.println(Thread.currentThread().getName()+":唤醒开始...");
notifyAll();
System.out.println(Thread.currentThread().getName()+":唤醒结束...");
}
}
class Mthread implements Runnable
{
private waitnotifyT waitnotifyT1;
private int flag=1;
public Mthread(waitnotifyT waitnotifyT1,int flag) {
this.waitnotifyT1 = waitnotifyT1;
this.flag=flag;
}
@Override
public void run()
{
if(flag==1)
{
waitnotifyT1.waitMethod();
}
else
waitnotifyT1.notifyMethod();
}
}
public class Wait1
{
public static void main(String[] args) throws InterruptedException {
waitnotifyT wn=new waitnotifyT();
Mthread waitThread=new Mthread(wn,1);
Mthread notifyThread=new Mthread(wn,0);
new Thread(waitThread,"等待线程1").start();
new Thread(waitThread,"等待线程2").start();
new Thread(waitThread,"等待线程3").start();
new Thread(waitThread,"等待线程4").start();
new Thread(waitThread,"等待线程5").start();
new Thread(notifyThread,"唤醒线程").start();
}
}
出现线程阻塞的几种情况:
1.调用sleep( )方法,主动放弃占有的cpu,不会释放对象锁
2.调用阻塞式IO方法(read( )、write( )),在该方法前,线程阻塞;
3.线程试图获取某个monitor,但该monitor被其他线程所持有导致阻塞;
4.线程等待某个通知,即调用wait( )方法(join方法是wait方法一层包装,也会出现线程阻塞);释放对象锁;
5.调用线程suspend(),将线程挂起,容易导致死锁,已被废弃。
run()方法运行结束后,会进入销毁阶段,整个线程执行完毕。
同步队列和等待队列:
每一个对象监视器monitor都有2个队列,一个称同步队列,一个称等待队列。
- 同步队列:因为竞争monitor(对象锁)失败导致阻塞的线程,这些线程等待cpu再次调度。或者说同步队列存储了将要获得锁的线程。
- 等待队列:因为调用wait()导致等待的线程,唤醒后进入同步队列竞争锁。唤醒后不会立即竞争锁,而是先进入同步队列,然后等待cpu调度再次竞争锁。
单个生产者消费模型:
一个生产者,一个消费者。对生产者来说,当生产数量大于0时,即还有库存,即先等消费者消费;对消费者来说,如果产品数量小于等于0,需要先den生产者生产。
代码如下:
package CODE.多线程;
////生成者消费者模型
class Goods
{
private String goodsName; //商品名称
private int counts; //商品数量
synchronized public void pro(String goodsName)
{
//产品数量大于0,等待消费者消费
if(counts>0)
{
try {
System.out.println("等一会生产,还有库存");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
counts++;
this.goodsName=goodsName;
System.out.println(Thread.currentThread().getName()+"生产"+this.goodsName+":"+counts);
notify(); //生产结束后,唤醒等待的消费者
}
synchronized public void Con()
{
//产品数量为0,等待生产者生产
if(counts==0)
{
try {
System.out.println("等一会来买,没有库存");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
counts--;
System.out.println(Thread.currentThread().getName()+"消费"+this.goodsName+":"+counts);
notify(); //唤醒等待的生产者
}
}
class Trade implements Runnable
{
private Goods goods;
//生产者生产产品
private int flag;
public Trade(Goods goods, int flag) {
this.goods = goods;
this.flag = flag;
}
@Override
public void run()
{
if(flag==1)
{
goods.pro("奥利奥饼干");
}
else
{
goods.Con();
}
}
}
public class ProCon {
public static void main(String[] args)
{
Goods goods=new Goods();
new Thread(new Trade(goods,1),"生产者1").start();
new Thread(new Trade(goods,0),"消费者1").start();
}
}
以上针对的是生产一次,消费一次。但是在实际生活中更多的是生产多次,消费多次。即多生产多消费。
多生产多消费:
多生产:可以多生产的产品数量有一个上限;
多消费:消费者是只要有产品就可以消费。
package CODE.多线程;
////生成者消费者模型
import java.util.ArrayList;
import java.util.List;
class MoreGoods
{
private String goodsName; //商品名称
private int counts; //商品数量
synchronized public void pro(String goodsName) {
//20ms生产一个商品
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
counts++;
this.goodsName=goodsName;
System.out.println(Thread.currentThread().getName()+"生产"+this.goodsName+":"+counts);
notifyAll(); //唤醒等待的消费者
}
synchronized public void Con()
{
//用循环是因为如果一消费者等待一会后,另一个消费者将唯一的产品消费,那么该消费者就需要再重新判断是否有资源消费
while (counts <= 0) {
try {
System.out.println("等一会来买,没有库存");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//100ms消费一个
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
counts--;
System.out.println(Thread.currentThread().getName() + "消费" + this.goodsName + ":" + counts);
}
public int getCounts() {
return counts;
}
}
class MoreTrade implements Runnable
{
private MoreGoods goods;
//生产者生产产品
private int flag;
public MoreTrade(MoreGoods goods, int flag) {
this.goods = goods;
this.flag = flag;
}
@Override
public void run() {
if (flag == 1) {
while (goods.getCounts() < 200) {
goods.pro("奥利奥饼干");
}
} else {
while (true)
goods.Con();
}
}
}
public class MorePro {
public static void main(String[] args) {
MoreGoods goods = new MoreGoods();
List<Thread> list=new ArrayList<>(); //数组存的是生产消费线程,因为生产消费线程需要同时启动
//5个生产者
for(int i=0;i<5;i++)
{
Thread thread=new Thread(new MoreTrade(goods,1),"生产者"+i);
list.add(thread);
}
//3个消费者
for(int i=0;i<3;i++)
{
Thread thread=new Thread(new MoreTrade(goods,0),"消费者"+i);
list.add(thread);
}
for(Thread thread:list) {
thread.start(); //将所有线程都启动
}
}
}