第二十二讲 多线程——多线程间的通信——多个生产者和消费者的升级解决方案

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yerenyuan_pku/article/details/82830996

这里我也是采用循序渐进的方式来讲解JDK1.5版本中提供的多线程升级解决方案,希望能更加容易地让大家接受。
为了解决多生产多消费的效率低下这一核心问题,在这儿我就告诉大家势必要用到JDK1.5中java.util.concurrent.locks包中的对象,其中就有Lock接口。须知同步代码块或者同步函数的锁操作是隐式的,而JDK1.5中出现的Lock接口按照面向对象的思想,将锁单独封装成了一个对象,并提供了对锁的显示操作,诸如以下操作:

  • lock():获取锁;
  • unlock():释放锁。

总而言之,Lock接口的出现比synchronized有了更多的操作,它就是同步的替代。所以我们首先将多个生产者和消费者案例中的同步更换为Lock接口的形式,代码如下,供大家参考。

import java.util.concurrent.locks.*;
// 描述资源
class Res
{
    private String name; // 资源名称
    private int count = 1; // 资源编号
    // 创建新Lock
    private Lock lock = new ReentrantLock();
    // 定义标记。
    private boolean flag;
    // 提供了给商品赋值的方法
    public void set(String name) //
    {
        // 获取锁。
        lock.lock();

        try
        {
            while (flag) // 判断标记为true,就执行wait()等待。为false,就生产。
                try{this.wait();}catch(InterruptedException e){ } // t0(等待)、t1(等待)
            this.name = name + "--" + count; // 面包1、面包2、面包3

            count++; // 4
            System.out.println(Thread.currentThread().getName() + "...生产者..." + this.name); // t0、面包1  t0、面包2、t1、面包3
            // 生产完毕,将标记改为true。
            flag = true;

            // 唤醒所有等待线程(包括本方线程)。
            this.notifyAll();
        }
        finally
        {
            // 释放锁。
            lock.unlock();
        }
    }

    // 提供获取一个商品的方法
    public void get() // t3
    {
        lock.lock();
        try
        {
            while (!flag)
                try{this.wait();}catch(InterruptedException e){ } // t2(等待)、t3(等待)
            System.out.println(Thread.currentThread().getName() + ".......消费者......." + this.name); // t2、面包1

            // 将标记改为false。
            flag = false;
            // 唤醒所有等待线程(包括本方线程)。
            this.notifyAll();
        }
        finally
        {
            lock.unlock();
        }

    }
}

// 生成者
class Producer implements Runnable
{
    private Res r; 
    Producer(Res r)
    {
        this.r = r;
    }
    public void run()
    {
        while (true)
            r.set("面包");
    }
}

// 消费者
class Consumer implements Runnable
{
    private Res r;
    Consumer(Res r)
    {
        this.r = r;
    }
    public void run()
    {
        while (true)
            r.get();
    }
}

class NewProducerConsumerDemo
{
    public static void main(String[] args) 
    {
        // 1、创建资源
        Res r = new Res();
        // 2、创建两个任务。
        Producer pro = new Producer(r);
        Consumer con = new Consumer(r);

        // 3、创建线程
        Thread t0 = new Thread(pro);
        Thread t1 = new Thread(pro);
        Thread t2 = new Thread(con);
        Thread t3 = new Thread(con);

        t0.start();
        t1.start();
        t2.start();
        t3.start();
    }
}

替换完之后,发现运行以上程序会报告如下异常,我截图如下。
在这里插入图片描述
究其原因就是wait()没有了同步区域,没有了所属的同步锁。同步升级了,其中锁已经不再是任意对象了,而是Lock类型的对象,那么和任意对象绑定的监视器方法,是不是也升级了,有了专门和Lock类型锁的绑定的监视器方法呢?通过查阅API帮助文档,可知Condition接口替代了Object类中的监视器方法。以前是监视器方法封装到了每一个对象中,现在是将监视器方法封装到了Condition对象中,方法名为await()、signal()、signalAll()。那么问题又来了,监视器对象Condition如何和Lock绑定呢?答案是可以通过Lock接口的newCondition()方法完成。
按照上面的分析,我将多个生产者和消费者案例的代码修改如下:

import java.util.concurrent.locks.*;
// 描述资源
class Res
{
    private String name; // 资源名称
    private int count = 1; // 资源编号
    // 创建新Lock
    private Lock lock = new ReentrantLock();
    // 创建和Lock接口绑定的监视器对象
    private Condition con = lock.newCondition();
    // 定义标记。
    private boolean flag;
    // 提供了给商品赋值的方法
    public void set(String name) //
    {
        // 获取锁。
        lock.lock();

        try
        {
            while (flag) // 判断标记为true,就执行wait()等待。为false,就生产。
                try{con.await();}catch(InterruptedException e){ } // t0(等待)、t1(等待)
            this.name = name + "--" + count; // 面包1、面包2、面包3

            count++; // 4
            System.out.println(Thread.currentThread().getName() + "...生产者..." + this.name); // t0、面包1  t0、面包2、t1、面包3
            // 生产完毕,将标记改为true。
            flag = true;

            // 唤醒所有等待线程(包括本方线程)。
            // this.notifyAll();
            con.signalAll();
        }
        finally
        {
            // 释放锁。
            lock.unlock();
        }
    }

    // 提供获取一个商品的方法
    public void get() // t3
    {
        lock.lock();
        try
        {
            while (!flag)
                try{con.await();}catch(InterruptedException e){ } // t2(等待)、t3(等待)
            System.out.println(Thread.currentThread().getName() + ".......消费者......." + this.name); // t2、面包1

            // 将标记改为false。
            flag = false;
            // 唤醒所有等待线程(包括本方线程)。
            // this.notifyAll();
            con.signalAll();
        }
        finally
        {
            lock.unlock();
        }

    }
}

// 生成者
class Producer implements Runnable
{
    private Res r; 
    Producer(Res r)
    {
        this.r = r;
    }
    public void run()
    {
        while (true)
            r.set("面包");
    }
}

// 消费者
class Consumer implements Runnable
{
    private Res r;
    Consumer(Res r)
    {
        this.r = r;
    }
    public void run()
    {
        while (true)
            r.get();
    }
}

class NewProducerConsumerDemo
{
    public static void main(String[] args) 
    {
        // 1、创建资源
        Res r = new Res();
        // 2、创建两个任务。
        Producer pro = new Producer(r);
        Consumer con = new Consumer(r);

        // 3、创建线程
        Thread t0 = new Thread(pro);
        Thread t1 = new Thread(pro);
        Thread t2 = new Thread(con);
        Thread t3 = new Thread(con);

        t0.start();
        t1.start();
        t2.start();
        t3.start();
    }
}

改完,运行以上程序,虽然运行没问题,但是问题依旧,一样唤醒了本方线程,效率仍旧低下!接下来我们就要看看如何真正解决多生产多消费的低效率问题了。
经过上面一步一步地分析,到这里,我们差不多可以写出真正解决多生产多消费效率低问题的程序了,现将代码贴出如下,以供大家参考。

import java.util.concurrent.locks.*;
// 描述资源
class Res
{
    private String name; // 资源名称
    private int count = 1; // 资源编号
    // 创建新Lock
    private Lock lock = new ReentrantLock();

    // 创建和Lock接口绑定的监视器对象。创建两个。
    // 生产者监视器
    private Condition producer_con = lock.newCondition();
    // 消费者监视器
    private Condition consumer_con = lock.newCondition();
    // 定义标记。
    private boolean flag;
    // 提供了给商品赋值的方法
    public void set(String name) //
    {
        // 获取锁。
        lock.lock();

        try
        {
            while (flag) // 判断标记为true,就执行wait()等待。为false,就生产。
                try{producer_con.await();}catch(InterruptedException e){ } // t0(等待)、t1(等待)
            this.name = name + "--" + count; // 面包1、面包2、面包3

            count++; // 4
            System.out.println(Thread.currentThread().getName() + "...生产者..." + this.name); // t0、面包1  t0、面包2、t1、面包3
            // 生产完毕,将标记改为true。
            flag = true;

            // 生产完毕,应该唤醒一个消费者来消费。
            consumer_con.signal();
        }
        finally
        {
            // 释放锁。
            lock.unlock();
        }
    }

    // 提供获取一个商品的方法
    public void get() // t3
    {
        lock.lock();
        try
        {
            while (!flag)
                try{consumer_con.await();}catch(InterruptedException e){ } // t2(等待)、t3(等待)
            System.out.println(Thread.currentThread().getName() + ".......消费者......." + this.name); // t2、面包1

            // 将标记改为false。
            flag = false;
            // 消费完后,应该唤醒一个生产者来生产。
            producer_con.signal();
        }
        finally
        {
            lock.unlock();
        }

    }
}

// 生成者
class Producer implements Runnable
{
    private Res r; 
    Producer(Res r)
    {
        this.r = r;
    }
    public void run()
    {
        while (true)
            r.set("面包");
    }
}

// 消费者
class Consumer implements Runnable
{
    private Res r;
    Consumer(Res r)
    {
        this.r = r;
    }
    public void run()
    {
        while (true)
            r.get();
    }
}

class NewProducerConsumerDemo
{
    public static void main(String[] args) 
    {
        // 1、创建资源
        Res r = new Res();
        // 2、创建两个任务。
        Producer pro = new Producer(r);
        Consumer con = new Consumer(r);

        // 3、创建线程
        Thread t0 = new Thread(pro);
        Thread t1 = new Thread(pro);
        Thread t2 = new Thread(con);
        Thread t3 = new Thread(con);

        t0.start();
        t1.start();
        t2.start();
        t3.start();
    }
}

猜你喜欢

转载自blog.csdn.net/yerenyuan_pku/article/details/82830996