Java实现多线程生产者消费模型及优化方案

生产者-消费者模型是进程间通信的重要内容之一。其原理十分简单,但自己用语言实现往往会出现很多的问题,下面我们用一系列代码来展现在编码中容易出现的问题以及最优解决方案。

/*  单生产者、单消费者生产烤鸭  */class Resource
{
    private String name;
    private int count = 1;  //计数器,记录有多少只烤鸭被生产及消费
    private boolean flag = false;  //停止标记
    public synchronized void set(String name)  //生产烤鸭方法
    {
        while(flag)  //如果flag=true,则等待
        {
            try{this.wait();}catch(InterruptedException e){}  //使用wait()方法必须要捕捉异常
        }
        this.name = name + count;
        count++;
        System.out.println(Thread.currentThread().getName()+"..."+this.name);
        flag = true;  //生产完一个烤鸭就将flag设为true,等待消费者消费烤鸭     notify();  //唤醒消费者
    }
    public synchronized void out()
    {
        while(!flag)
        {
            try{this.wait();}catch(InterruptedException e){}
        }
        System.out.println(Thread.currentThread().getName()+"........."+this.name);
        flag = false;  //消费完一个烤鸭就将flag设为false,等待生产者生产烤鸭
        notify();  //唤醒生产者
    }
}

class Producer implements Runnable
{
    Resource r;  
    Producer(Resource r)
    {
        this.r = r;
    }
    public void run()
    {
        while(true)
        {
            r.set("kaoya");  //生产烤鸭
        }
    }
}

class Consumer implements Runnable
{
    Resource r;
    Consumer(Resource r)
    {
        this.r = r;
    }
    public void run()
    {
        while(true)
        {
            r.out();  //消费烤鸭
        }
    }
}

public class ResourceDemo
{
    public static void main(String[] args)
    {
        Resource r = new Resource();
        Producer producer = new Producer(r);
        Consumer consumer = new Consumer(r);
        Thread t1 = new Thread(producer);
        Thread t2 = new Thread(producer);
        t1.start();
        t2.start();
    }
}

以上是单生产者单消费者的代码,我们来看一下运行结果:

然而正如实际情况,饭店的厨房里不可能只有一个厨子,也不可能只有一个顾客,因此只考虑单消费者单生产者是没有意义的。我们再各加入一个消费者和生产者线程。

很显然,运行中出现了死锁,程序卡住,这非常让人费解。

实际上,在加入两个线程时,整个程序的运行过程已经发生了很大的变化。起始flag为false,因此生产者线程t1可以进入同步区,完成后将flag设为true。在这种情况下,生产者线程t3会进入wait状态。与此同时,两个消费者线程t2和t4可能都会因为一开始flag为false进入wait状态。当线程t1完成整个流程准备notify()时,线程池里一共有三个线程:生产者t3,消费者t2、t4。由于notify()的工作机制是随机唤醒一个线程,最不巧的情况,如果它唤醒的是生产者线程t3,那么t3判断flag为true,又会进入wait状态。此时此刻,所有四个线程都会进入wait状态,自然地,发生了线程死锁。

那么如何解决?最简单的方法是将notify()改为notifyAll(),这样每一次都会唤醒所有的线程。然而这种方法实在是有一点浪费资源,因为本来我们只需要唤醒1个或2个线程,而使用notifyAll()则必须将三个线程都唤醒。面试进行到这里,肯定会有一个问题——如何优化?

我们可以使用JDK1.5的特性Lock来处理,在使用它时需要在头部import java.util.concurrent.locks.*;它可以实现仅唤醒消费者线程或生产者线程,赋予了程序员更多的控制权,自然也能提高程序的效率,使得不必唤醒不需要唤醒的线程。具体代码如下:

import java.util.concurrent.locks.*;

class Resource
{
    private String name;
    private int count = 1;
    private boolean flag = false;
    Lock lock = new ReentrantLock();  //新建锁对象
    Condition producer_con = lock.newCondition();  //新建生产者condition
    Condition consumer_con = lock.newCondition();  //新建消费者condition
    public void set(String name)
    {
        lock.lock();  //上锁
        try
        {
            while(flag)
            {
                try{producer_con.await();}catch(InterruptedException e){}  //注意在这种情况下要将原来的this.wait()改为producer_con.await
            }
            this.name = name + count;
            count++;
            System.out.println(Thread.currentThread().getName()+"..."+this.name);
            flag = true;
            consumer_con.signal();  //唤醒消费者线程
        }
        finally
        {
            lock.unlock();  //把解锁放在finally里,不管中间是否有异常一定要解锁
        }
    }
    public void out()
    {
        lock.lock();
        try
        {
            while(!flag)
            {
                try{consumer_con.await();}catch(InterruptedException e){}
            }
            System.out.println(Thread.currentThread().getName()+"........."+this.name);
            flag = false;
            producer_con.signal();  //唤醒生产者线程
        }
        finally
        {
            lock.unlock();
        }
    }
}

class Producer implements Runnable
{
    Resource r;
    Producer(Resource r)
    {
        this.r = r;
    }
    public void run()
    {
        while(true)
        {
            r.set("kaoya");
        }
    }
}

class Consumer implements Runnable
{
    Resource r;
    Consumer(Resource r)
    {
        this.r = r;
    }
    public void run()
    {
        while(true)
        {
            r.out();
        }
    }
}

public class ResourceDemo
{
    public static void main(String[] args)
    {
        Resource r = new Resource();
        Producer producer = new Producer(r);
        Consumer consumer = new Consumer(r);
        Thread t1 = new Thread(producer);
        Thread t2 = new Thread(consumer);
        Thread t3 = new Thread(producer);
        Thread t4 = new Thread(consumer);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

运行结果,如丝般顺滑:

时间: 2024-12-23 22:13:24

Java实现多线程生产者消费模型及优化方案的相关文章

Java里的生产者-消费者模型(Producer and Consumer Pattern in Java)

生产者-消费者模型是多线程问题里面的经典问题,也是面试的常见问题.有如下几个常见的实现方法: 1. wait()/notify() 2. lock & condition 3. BlockingQueue 下面来逐一分析. 1. wait()/notify() 第一种实现,利用根类Object的两个方法wait()/notify(),来停止或者唤醒线程的执行:这也是最原始的实现. 1 public class WaitNotifyBroker<T> implements Broker&

并发编程【四】锁和队列及生产者消费模型

1.锁multiprocessing-Lock 锁的应用场景:当多个进程需要操作同一个文件/数据的时候: 当多个进程使用同一份数据资源的时候,就会引发数据安全或顺序混乱问题. 为保证数据的安全性,多进程中只有去操作一些进程之间可以共享的数据资源的时候才需要进行加锁: 枷锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务进行修改,即串行的修改,没错速度式慢了,但牺牲了速度却保证了数据的安全: 模拟查票抢票: import json import time from multiprocess

实现同步与互斥的一个实例--生产者消费模型

1.基础知识 1).生产者消费模型中存在3种关系: a.生产者与生产者之间是互斥的: b.消费者与消费者之间是互斥的: c.生产者与消费者之间是同步与互斥的: 2).生产者与消费者的场所,这儿我们选择单链表. 2.内容:多生产者生产一个结构体串在链表的表尾上,多消费者从表头取走结构体. 3.代码实现   1 #include<stdio.h>   2 #include<stdlib.h>   3 #include<pthread.h>   4    5 typedef 

python GIL锁 锁 线程池 生产者消费模型

python的GIL 锁 python内置的一个全局解释器锁 , 锁的作用就是保证同一时刻一个进程中只有一个线程可以被cpu调度 为什么有这把GIL锁? python语言的创始人在开发这门语言时 , 目的快速把语言开发出来 , 如果加上GIL锁(C语言加锁) , 切换时按照100条字节指令来进行线程间的切换 锁 : 1.锁 : Lock(1次放1个) 线程安全 , 多线程操作时 , 内部会让所有线程排队处理 , 如 : list / dict / Queue 线程不安全 + 人  =>排队处理

[多线程] 生产者消费者模型的BOOST实现

说明 如果 使用过程中有BUG 一定要告诉我:在下面留言或者给我邮件(sawpara at 126 dot com) 使用boost::thread库来实现生产者消费者模型中的缓冲区! 仓库内最多可以存放 capacity 个产品. 条件变量 condition_put 标记是否可以往仓库中存放一个产品. 条件变量 condition_get 标记是否可以从仓库中取出一个产品. 互斥量 mutexer 用于保证当前仓库只有一个线程拥有主权. 实现 #include <queue> #inclu

多线程,生产者消费者模型(生产馒头,消费馒头)

先建立一个容器 /** * 容器 * 共享资源 * @author Administrator * */ public class SynStack { int index = 0; //容器 SteamBread[] stb = new SteamBread[6]; /** * 往容器中放产品 */ public synchronized void push(SteamBread st){ while(index == stb.length){ try { this.wait(); } cat

java并发之生产者消费者模型

生产者和消费者模型是操作系统中经典的同步问题.该问题最早由Dijkstra提出,用以演示它提出的信号量机制. 经典的生产者和消费者模型的描写叙述是:有一群生产者进程在生产产品.并将这些产品提供给消费者进程去消费.为使生产者进程与消费者进程能并发执行,在两者之间设置了一个具有n个缓冲区的缓冲池,生产者进程将它所生产的产品放入一个缓冲区中.消费者进程可从一个缓冲区中取走产品去消费.虽然全部的生产者进程和消费者进程都是以异步方式执行的.但它们之间必须保持同步,即不同意消费者进程到一个空缓冲区去取产品,

基于POSIX信号量实现生产者消费模型

一.基础知识 1.1 system V版本的信号量和POSIX下信号量的区别 我们前面讲过进程间通信有三种形式:管道.消息队列.信号量.这个信号量是system V版本下,以信号量集形式申请存在的,它标识着一个临界资源的有无从而控制不同的进程能否访问到该临界资源.但,现在,我们要讲的信号量是基于POSIX下的信号量,它用来标识资源的个数. 1.2 互斥锁和信号量 上篇所述,互斥锁(Mutex)可看作是某种资源的可用数,Mutex变量是非0即1的,初始化时Mutex为1,表示有一个可用资源:加锁时

java程序中sql注入分析及优化方案

先来看看百度百科的解释: 所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令.具体来说,它是利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句.比如先前的很多影视网站泄露VIP会员密码大多就是通过WEB表单递交查询字符暴出的,这类表单特别容易受到sql注入攻击. 1.java程序