Fork me on GitHub

Java 多线程通信之多生产者/多消费者

// 以生产和消费烤鸭为例
class Resource
{
    private String name;
    private int count = 1; // 记录烤鸭的编号
    private boolean flag = false;

    public synchronized void set(String name)
    {
        if(flag)
            try{this.wait();}catch(InterruptedException e){}
        this.name = name + count;
        count++;
        System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);
        flag = true;
        this.notify();
    }

    public synchronized void out()
    {
        if(!flag)
            try{this.wait();}catch(InterruptedException e){}
        Sytem.out.println(Thread.currentThread().getName()+ "...消费者.."+ this.name);
        flag = false;
        this.notify();
    }
}

class Producer implements Runnable
{
    Resource r;
    Producer(Resource r)
    {
        this.r = r;
    }

    public void run()
    {
        while(true)
        {
            r.set("烤鸭");
        }
    }
}

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

class ProducerConsumerDemo
{
    public static void main(String[] args)
    {
        // 创建资源
        Resource r = new Resource();

        // 创建任务
        Producer pro = new Producer(r);
        Consumer con = new Consumer(r);

        // 多生产者
        Thread t0 = new Thread(pro);
        Thread t1 = new Thread(pro);

        // 多消费者
        Thread t2 = new Thread(con);
        Thread t3 = new Thread(con);
        Thread t4 = new Thread(con);
        Thread t5 = new Thread(con);

        // 开启线程
        t0.start();
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

出现错误的两种情况:

  1. 线程安全问题(虚假唤醒): 线程1 生产的烤鸭被线程3 和线程5 两个线程同时消费
    • if 只能判断标记一次, 会导致不该运行的线程运行了, 出现数据错误的情况
    • while 可以多次判断标记, 解决了线程获取执行权后, 是否要运行的问题!

  1. 死锁
    • notify() 一次只能唤醒一个线程, 如果本方唤醒类本方, 没有意义. 而且 while 判断标记多次,
      会导致死锁.
    • notifyAll() 解决了本方线程一定会唤醒对方线程的问题.
// 升级版代码
class Resource
{
    private String name;
    private int count = 1; // 记录烤鸭的编号
    private boolean flag = false;

    public synchronized void set(String name)
    {
        // 将 if 换为 while, 线程从冻结状态被唤醒后,需要判断 flag 标记之后, 确定是否继续生产"烤鸭"
        while(flag)  
            try{this.wait();}catch(InterruptedException e){}
        this.name = name + count;
        count++;
        System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);
        flag = true;
        this.notifyAll(); // 肯定会唤醒对方的线程, 解决了死锁问题
    }

    public synchronized void out()
    {
        while(!flag)
            try{this.wait();}catch(InterruptedException e){}
        Sytem.out.println(Thread.currentThread().getName()+ "...消费者.."+ this.name);
        flag = false;
        this.notifyAll();
    }
}

class Producer implements Runnable
{
    Resource r;
    Producer(Resource r)
    {
        this.r = r;
    }

    public void run()
    {
        while(true)
        {
            r.set("烤鸭");
        }
    }
}


- [JavaSE 基础视频(毕向东)](https://www.bilibili.com/video/av3106510/#page=4)
posted @ 2017-08-31 10:57  小a的软件思考  阅读(245)  评论(0编辑  收藏  举报