java多线程学习--生产者消费者问题

生产者消费者问题是一个经典的同步问题,曾经在操作系统中接触过,还有哲学家就餐问题.
今天在学习多线程时,又遇到这个例子,今天就讨论一下生产者消费者问题的解决方法.
1.一对一
代码实现

public class Resouce {

    private String name;
    //计数器
    private int count = 1;
    private Boolean flag = false;

    public synchronized void set() {
        if(flag)
            try {
                wait();
            }catch(Exception e) {
            }
        this.name = "+商品+"+count++;

        System.out.println(Thread.currentThread().getName()+
            "生产者"+"...."+name);
        //置标志为真
        flag=true;
        //唤醒消费者进程
        this.notify();
    }

    public synchronized void out() {
        if(!flag)
            try {//flag为假,等待
                wait();
            }catch(Exception e) {}
        //打印消费者进程
        System.out.println(Thread.currentThread().getName()+
            "消费者"+"......."+name);
        //置标志为假
        flag=false;
        //唤醒生产者进程
        this.notify();
    }


}
public class Producer implements Runnable {
    //生产者
    private Resouce r;
    public Producer(Resouce r) {
        this.r = r;
    }

    public void run() {

        while(true) {
            r.set();
        }
    }

}
public class Consumer implements Runnable {
    //消费者
    private Resouce res;

    public Consumer(Resouce res) {
        this.res= res;
    }

    public void run() {
        while(true) {
            res.out();
        }
    }

}

public class ProducerAndConsumer {

    //主线程
    public static void main(String[] args) {
        //资源
        Resouce res = new Resouce();
        //创建生产者消费者对象
        Producer p = new Producer(res);
        Consumer con = new Consumer(res);
        //为对象创建线程
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(con);
        //开启线程
        t1.start();
        t2.start();
    }
}

eclipse中运行太快,用cmd运行结果
这里写图片描述
从图片中可以看出,生产者生产一个商品,消费者就消费一个,而且往下执行,也没有发现错误,说明这个一对一解决了.
2.多对多
接下来,看多对多,为简单起见,再创建一个消费者进程和生产者进程,在主函数中创建即可
观察运行结果
这里写图片描述
很给力,刚开始运行就出现了错误,商品2被消费了好多次,商品3没有被消费
这是为什么呢?
跟着代码走一遍
**假设第一个生产者线程先进来判断if(flag),不成立,往下执行,生产完产品后,置flag为true,此时线程池中没有线程等待,第一个生产者线程继续执行set函数,if(flag成立),wait()
这里写图片描述
此时,t1失去资格
**这时第二个生产者线程进来,在同样的地点失去资格
这里写图片描述
**然后消费者线程t3登场,flag=true,执行消费函数后,置flag为false,并唤醒t1,因为flag为false,t3wait()
这里写图片描述
**同样,第二个消费者线程t4进场,在同样的地点wait(),flag=false
这里写图片描述
**这时t1是醒着的,继续往下执行,置flag为true,并唤醒线程池中第一个进程,往前看,可以发现,第一个进程是t2,唤醒t2,然后t1继续执行,在同样的地点wait()
**t2获得CPU执行权,因为t2在wait之前就已经判断过if(flag),从wait的地方继续往后执行.
即使这时候false=true,理论上t2不应该执行.这就导致了t1生产的商品没有被消费
继续按上述逻辑,不难退出一个商品被消费多次的情况.
这种情况的出现,是由于if(flag)只是判断一次,在if下wait的线程获得执行权,就不用再次判断flag状态了,显然是不安全的,那么怎么使程序多次判断flag状态呢?
很简单,用while就可以
用while之后的执行结果
这里写图片描述
程序好像出问题了呢,是死锁吗,什么原因?
让我们回到分析的最后一步.在代码中,我们使用的是notify()函数,每次只唤醒线程池中的第一个线程.所以最后一步中,t2是被t1唤醒的,此时的flag=true,t2–>wait(),但是他没有唤醒任何一个进程,也就是说线程池中的生产者和消费者进程都在wait(),并不是死锁.
怎么解决?
将notify()改为notifyAll()就可以,notifyAll()函数是唤醒线程池中所有等待的线程.

最终代码

class Resouce {

    private String name;
    //计数器
    private int count = 1;
    private Boolean flag = false;

    public synchronized void set() {
        while(flag)
            try {
                wait();
            }catch(Exception e) {
            }
        this.name = "+商品+"+count++;

        System.out.println(Thread.currentThread().getName()+
            "生产者"+"...."+name);
        //置标志为真
        flag=true;
        //唤醒消费者进程
        this.notifyAll();
    }

    public synchronized void out() {
        while(!flag)
            try {//flag为假,等待
                wait();
            }catch(Exception e) {}
        //打印消费者进程
        System.out.println(Thread.currentThread().getName()+
            "消费者"+"......."+name);
        //置标志为假
        flag=false;
        //唤醒生产者进程
        this.notifyAll();
    }


}
class Producer implements Runnable {
    //生产者
    private Resouce r;
    public Producer(Resouce r) {
        this.r = r;
    }

    public void run() {

        while(true) {
            r.set();
        }
    }

}
class Consumer implements Runnable {
    //消费者
    private Resouce res;

    public Consumer(Resouce res) {
        this.res= res;
    }

    public void run() {
        while(true) {
            res.out();
        }
    }

}

public class test {

    //主线程
    public static void main(String[] args) {
        //资源
        Resouce res = new Resouce();
        //创建生产者消费者对象
        Producer p = new Producer(res);
        Consumer con = new Consumer(res);
        //为对象创建线程
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(con);
        Thread t3 = new Thread(p);
        Thread t4 = new Thread(con);
        //开启线程
        t1.start();
        t3.start();

        t2.start();
        t4.start();
    }
}

运行结果
这里写图片描述

jdk升级至1.5之后的解决方法

在jdk1.5之后,提供了多线程的升级结局方案,将同步的Synchronized替换成显式的Lock操作
将object类中的wait,notify,notifyAll,替换成了Condition对象,该对象可以通过lock对象获取
接口类Lock,Condition ,方法:await(),signal(),signalAll()对应的等待和唤醒操作

Lock接口中,有lock()和unlock()方法,前者是获取锁,后者释放锁.
在本问题中,通过彼此唤醒来实现消费者生产者的同步问题的解决

实现代码

import java.util.concurrent.locks.*;

class Resouce {

    private String name;
    //计数器
    private int count = 1;
    private Lock lock = new ReentrantLock();
    private Boolean flag = false;
    private Condition condition_pro = lock.newCondition();
    private Condition condition_con = lock.newCondition();



    public void set() throws InterruptedException {
        lock.lock();
        try {
            while(flag)
                condition_pro.await();
            this.name = "+商品+"+count++;

            System.out.println(Thread.currentThread().getName()+"生产者"+"...."+name);
            //置标志为真
            flag=true;
            //唤醒消费者进程
            condition_con.signal();
        }finally {
            //解除锁
            lock.unlock();
        }
    }

    public void out() throws InterruptedException {
        //获取锁
        lock.lock();
        try {
            while(!flag)
                condition_con.await();
            //打印消费者进程
            System.out.println(Thread.currentThread().getName()+"消费者"+"......."+name);
            //置标志为假
            flag=false;
            //唤醒生产者进程
            condition_pro.signal();
        }finally {
            //解除锁
            lock.unlock();
        }
    }


}
class Producer implements Runnable {
    //生产者
    private Resouce r;
    public Producer(Resouce r) {
        this.r = r;
    }

    public void run() {

        while(true) {
            try {
                r.set();
            } catch (InterruptedException e) {}
        }
    }
}
class Consumer implements Runnable {
    //消费者
    private Resouce res;

    public Consumer(Resouce res) {
        this.res= res;
    }

    public void run() {
        while(true) {
            try {
                res.out();
            } catch (InterruptedException e) {}
        }
    }

}


public class ProdecerAndConsumer2 {

    //主线程
    public static void main(String[] args) {
        //资源
        Resouce res = new Resouce();
        //创建生产者消费者对象
        Producer p = new Producer(res);
        Consumer con = new Consumer(res);
        //为对象创建线程
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(con);
        Thread t3 = new Thread(p);
        Thread t4 = new Thread(con);
        //开启线程
        t1.start();
        t3.start();

        t2.start();
        t4.start();
    }
}

猜你喜欢

转载自blog.csdn.net/fly_fly_fly_pig/article/details/81115382
今日推荐