Java 线程之生产者消费者

在java多线程编程中,生产者和消费者问题,一直都是一个非常经典的问题,也是充分利用线程同步,对象锁等概念的具体实现,通常情况下也有很多地方能够使用到这种编程模型,下面通过几个例子来简单说明下,解决生产者消费者问题的方法

   一.使用同步锁,以及wait和notify来解决生产者消费者问题,首先我们来看看下面的代码:

</pre><span style="font-family:FangSong_GB2312; font-size:14px">对于生产者的代码实现为:</span><p></p><p><span style="font-family:FangSong_GB2312; font-size:14px"></span></p><pre name="code" class="java">public class Producers extends Thread {

    // 每次生产的产品数量
    private int num = 3;

    private ProductStore myProductStore;

    public Producers(ProductStore myProductStore) {

        this.myProductStore = myProductStore;
    }

    @Override
    public void run() {
        super.run();
        if (myProductStore != null) {
            myProductStore.produce(num);
        }
    }

    public ProductStore getMyProductStore() {
        return myProductStore;
    }

    public void setMyProductStore(ProductStore myProductStore) {
        this.myProductStore = myProductStore;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

}
对于消费者的代码实现为:
public class Consumers extends Thread {

    // 每次生产的产品数量
    private int num = 5;

    private ProductStore myProductStore;

    public Consumers(ProductStore myProductStore) {

        this.myProductStore = myProductStore;
    }

    @Override
    public void run() {
        super.run();
        if (myProductStore != null) {

            myProductStore.consumption(num);
        }
    }

    public ProductStore getMyProductStore() {
        return myProductStore;
    }

    public void setMyProductStore(ProductStore myProductStore) {
        this.myProductStore = myProductStore;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

}

产品库类实现:

public class ProductStore {


    // 产品库的最大容量
    private final int MAX_SIZE = 10;
    // 产品库
    private final ArrayList<Object> storeList = new ArrayList<Object>();


    /**
     * 
     * @Title: produce
     * @Description: 生成num个产品
     * @date 2014-8-18
     * @version 1.0
     */
    public void produce(int num) {


        synchronized (storeList) {


            while (storeList.size() + num > MAX_SIZE) {
                System.out.println("需要创建的产品已经超出了,仓库的容量");
                try {
                    storeList.notifyAll();
                    // 生成线程阻塞
                    storeList.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }


            for (int i = 0; i < num; i++) {


                System.out.println("create----------->");
                storeList.add(new Object());
            }
            storeList.notifyAll();
        }


    }


    /**
     * 
     * @Title: consumption
     * @Description: 消费num件商品
     * @date 2014-8-18
     * @version 1.0
     */
    public void consumption(int num) {


        synchronized (storeList) {


            while (storeList.size() < num) {
                try {


                    storeList.notifyAll();
                    // 生成线程阻塞
                    storeList.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 否则消费num个产品
            for (int i = 0; i < num; i++) {
                System.out.println("remove----------->");
                storeList.remove(0);
            }
            storeList.notifyAll();
        }


    }


}
</pre></p>你可以简单的做一些测试,就是同时建立几个生产者,几个消费者线程,同时运行然后观察效果,这里我们要分析下产品库类的相关代码,其中MAX_SIZE为产品库最大的容量,这个容量的限制是当产品库的容量大于了MAX_SIZE时先唤醒,处于等待状态的消费者线程,然后让调用了产品库的wait 方法让生产者线程停止执行,并且释放同步锁,其他线程就可以获得该同步锁,并且开始执行相关的操作,当产品库位<p><span style="font-family:FangSong_GB2312;font-size:14px;"> 1.你可能会疑问为什么这里使用while而不是使用if来判断呢,实际上因为我们目前的操作是在多线程的环境下进行的,也就是说当其他线程调用了notifyAll后,我们的执行是从wait的下一句代码开始执行的,如果这里不用while而是用if,那么他没有进行条件的判断,直接跳出判断条件往下走,这时候,可能其他的线程并没有完成生产或者消费,可能相关的条件并不满足,这时候我们往下执行就会产生一些问题。</span></p><p><span style="font-family:FangSong_GB2312;font-size:14px;"> 2.<span style="margin: 0px; padding: 0px; line-height: 23px; ">wait</span><span style="margin: 0px; padding: 0px; line-height: 23px; ">被调用的时候必须在拥有锁(即</span><span style="margin: 0px; padding: 0px; line-height: 23px; ">synchronized</span><span style="margin: 0px; padding: 0px; line-height: 23px; ">修饰的)的代码块中,也就是说wait必须在同步代码块中进行调用。</span></span></p><p><span style="font-family:FangSong_GB2312;font-size:14px;"><span style="margin: 0px; padding: 0px; line-height: 23px; "> 3.<span style="color: rgb(51, 51, 51); line-height: 23px; ">若</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">wait</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">方法参数中带时间,则除了</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">notify</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">和</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">notifyAll</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">被调用能激活处于</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">wait</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">状态(等待状态)的线程进入锁竞争外,在其他线程中</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">interrupt</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">它或者参数时间到了之后,该线程也将被激活到竞争状态。</span></span></span></p><p><span style="font-family:FangSong_GB2312;font-size:14px;color:#333333;"><span style="line-height: 23px;"> 4.<span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">wait</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">方法被调用的线程必须获得之前执行到</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">wait</span><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; ">时释放掉的锁重新获得才能够恢复执行。</span></span></span></p><p><span style="font-family:FangSong_GB2312;font-size:14px;color:#333333;"><span style="line-height: 23px;"><span style="margin: 0px; padding: 0px; color: rgb(51, 51, 51); line-height: 23px; "> 5.notify 和notifyAll 的区别是,notify是唤醒某一个在该对象锁上处于等待状态的线程,注意这里我们并不知道,具体是那个线程,如果有多个线程,当其中一个线程被唤醒并完成线程的执行后,其他的线程如果没有在任何时机,有调用notify或者notifyAll都将处于等待状态,不会因为现在资源已经可用了而自动唤醒,除非有其他地方调用了,notify或者notifyAll,而notifyAll则是唤醒所有在改对象锁基础上,处于等待状态的线程,他们可用平等的竞争锁资源,这里可以根据实际的情况决定使用哪种锁。</span></span></span></p><p><span style="font-family:FangSong_GB2312;font-size:14px;color:#333333;"><span style="line-height: 23px;"> 6.关于<span style="font-family: FangSong_GB2312;font-size:14px; line-height: 23px; ">synchronized,在JDK1.5后我们也可以使用lock来实现相同的效果。</span></span></span></p><p><span style="font-family:FangSong_GB2312;font-size:14px;"><strong>二.使用<span style="line-height: 26px; text-indent: 28px; ">阻塞队列来实现:</span></strong></span></p><p style="text-indent: 28px;"><span style="font-family:FangSong_GB2312;font-size:14px;"><span style="line-height: 26px;">在JDK1.5后,java提出了阻塞队列,使用阻塞队列我们可以很简单的解决生产者消费者问题</span></span></p><p style="text-indent: 28px;"><span style="font-family:FangSong_GB2312;font-size:14px;"><span style="line-height: 26px;"></span></span><pre name="code" class="java">public class ProductStore {

    // 产品库的最大容量
    private final int MAX_SIZE = 10;
    // 产品库
    private final LinkedBlockingQueue<Object> storeList = new LinkedBlockingQueue<Object>();

    /**
     * 
     * @Title: produce
     * @Description: 生成num个产品
     * @date 2014-8-18
     * @version 1.0
     */
    public void produce(int num) {

        for (int i = 0; i < num; i++) {
            try {
                // 放入产品,自动阻塞
                storeList.put(new Object());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 
     * @Title: consumption
     * @Description: 消费num件商品
     * @date 2014-8-18
     * @version 1.0
     */
    public void consumption(int num) {
        for (int i = 0; i < num; i++) {
            try {
                // 放入产品,自动阻塞
                storeList.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
对阻塞队列继续以下说明 当缓冲区已满,生产者在 put() 操作时, put() 内部调用了 await() 方法,放弃了线程的执行,线程的执行会在put函数调用的地方阻塞下来,直到有其他的线程调用了take方法,take()内部调用了signal()方法,通知生产者线程可以执行。这样我们就解决了生产者消费者问题。

三.使用PipedInputStream/PipedOutputStream来实现:

 在JDK1.5后,java提出了管道流,就像其他的输入输入输出流一样,向输入流里面输入数据,从输出流里面获得数据,这样就实现类似一个生产者消费者的模式,但是java的管道流还不允许传输对象。

在java中,PipedOutputStreamPipedInputStream分别是管道输出流和管道输入流。它们的作用是让多线程可以通过管道进行线程间的通讯。在使用管道通信时,必须将PipedOutputStream和PipedInputStream配套使用。使用管道通信时,大致的流程是:我们在线程A中向PipedOutputStream中写入数据,这些数据会自动的发送到与PipedOutputStream对应的PipedInputStream中,进而存储在PipedInputStream的缓冲中;此时,线程B通过读取PipedInputStream中的数据。就可以实现,线程A和线程B的通信

参考文献:

1、http://www.cnblogs.com/lich/archive/2011/12/11/2283928.html

2、http://www.cnblogs.com/skywang12345/p/io_04.html

http://www.cnblogs.com/skywang12345/p/io_04.html 中详细的介绍管道流的相关使用和java源码。

四.使用await()/signal()来实现:

JDK5.0之后,Java提供了更加健壮的线程处理机制,包括同步、锁定、线程池等,它们可以实现更细粒度的线程控制。await()signal()就是其中用来做同步的两种方法,它们的功能基本上和wait() / nofity()相同,完全可以取代它们,但是它们和新引入的锁定机制Lock直接挂钩,具有更大的灵活性。通过在Lock对象上调用newCondition()方法,将条件变量和一个锁对象进行绑定,进而控制并发程序访问竞争资源的安全。

</pre><pre name="code" class="java">public class ProductStore {


    // 产品库的最大容量
    private final int MAX_SIZE = 10;
    // 产品库
    private final ArrayList<Object> storeList = new ArrayList<Object>();
    // 锁
    private final Lock lock = new ReentrantLock();


    // 仓库满的条件变量
    private final Condition full = lock.newCondition();


    // 仓库空的条件变量
    private final Condition empty = lock.newCondition();


    /**
     * 
     * @Title: produce
     * @Description: 生成num个产品
     * @date 2014-8-18
     * @version 1.0
     */
    public void produce(int num) {


        // 获得锁
        lock.lock();
        while (storeList.size() + num > MAX_SIZE) {
            System.out.println("需要创建的产品已经超出了,仓库的容量");
            try {
                // 生成线程阻塞
                full.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


        for (int i = 0; i < num; i++) {


            System.out.println("create----------->");
            storeList.add(new Object());
        }
        // 唤醒其他所有线程
        empty.signalAll();
        // 释放锁
        lock.unlock();
    }


    /**
     * 
     * @Title: consumption
     * @Description: 消费num件商品
     * @date 2014-8-18
     * @version 1.0
     */
    public void consumption(int num) {


        // 获得锁
        lock.lock();


        while (storeList.size() < num) {
            try {
                // 生成线程阻塞
                empty.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 否则消费num个产品
        for (int i = 0; i < num; i++) {
            System.out.println("remove----------->");
            storeList.remove(0);
        }
        // 唤醒其他所有线程
        full.signalAll();
        // 释放锁
        lock.unlock();
    }


}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值