多线程经典消费者实例

需求

我们的程序想要实现这样的一个功能,两个线程,一个不断往一个容器加数据,一个不断从这个容器取数据。

设计的问题

我们的第一个问题是,如果容器满了怎么办,空了又怎么。解决的办法是使用wait()和notify()。思路是当容器满或空时,对应的线程就应该停下了,等到不空或者不满的时候再继续。显然wait()和notify()可以很好的实现。当空时,暂停取,使用wait(),添加线程添加了后就不空了,就可以使用notify()唤醒取的线程了。满的时候也是一样的。

sleep()和wait()的区别

暂停也可用sleep(),为什么不用sleep()呢?首先是我们不知道停多久,有notify()配合使用才方便。sleep()和wait()有一个重要的区别是,sleep()不会让出对象锁。我们写个例子来证明下:

class Lock{
    public synchronized void printlnTest(){
        for(int i=0;i<100;i++)
        System.out.println(Thread.currentThread().getName());
    }
    public synchronized void sleepTest(){

        System.out.println("sleeped 2 second");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

class Print implements Runnable{
    private Lock lock;
    public  Print(Lock lock){
        this.lock = lock;
    }
    @Override
    public void run(){
        lock.printlnTest();
    }
}

class SleepTest implements Runnable{
    private Lock lock;
    public  SleepTest(Lock lock){
        this.lock = lock;
    }
    @Override
    public void run(){
        lock.sleepTest();
    }
}
public class SleepAndWait {

    public static void main(String[] args) {
        Lock lock = new Lock();
        Print print = new Print(lock);
        SleepTest sleep = new SleepTest(lock);

        new Thread(sleep).start();
        new Thread(print).start();  
    }
}

输出的结果是,输出了sleeped 2 second,等待两秒后,才可以看到输出的100个Thread-1。如果把synchronized去掉,也就是不加锁,Thread-1会立即输出不会等两秒。这里证明sleep是不会让出对象锁的。

非常有用的线程图

这里写图片描述

消费者实例

这是容器,使用一个类似栈的容器

public class Stack {
    private char[] container  = new char[6];
    private int top;//指向栈顶元素的上一个

    public synchronized void push(char a){

        if(top==container.length){//if 改为while更合适,思考下为什么
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
            this.notify();
            container[top]=a;
            top++;
            System.out.println(Thread.currentThread().getName()+"入栈的元素"+a);

    }

    public synchronized char pop(){

        if(top==0){//if 改为while更合适,思考下为什么
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
            this.notify();
            top--;
            System.out.println(Thread.currentThread().getName()+"出栈的元素:"+container[top]);
            return container[top];

    }

}

生产者

public class Producer implements Runnable{
    private Stack stack;

    public Producer(Stack stack){
        this.stack = stack;
    }

    @Override
    public void run(){

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

            stack.push((char)('a'+i));
        }

    }
}

消费者

public class Consumer implements Runnable{

    private Stack stack;
    public Consumer(Stack stack){
        this.stack = stack;
    }

    @Override
    public void run(){

        try {
            for(int i=0;i<10;i++)
            stack.pop();

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }       
    }
}

主线程

public class ThreadTest {

    public static void main(String[] args) {

        Stack stack = new Stack();
        Producer p = new Producer(stack);
        Consumer c = new Consumer(stack);
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(c);

        t1.start();
        t2.start();
    }
}
同步的问题

当添加线程的push方法和取线程的pop方法,不是原子操作时意思就是push方法执行一半,cpu切换给pop方法的线程执行,就会出现问题。比如说,当执行了container[top]=a;但是还没执行top++;切换到取线程时,把原来栈顶的元素取出来了并且指针向下减,如果再返回添加线程,继续执行top++;结果就是新添加的元素丢失了,原来栈顶的元素取了一次还在。所以pop方法和push方法要加锁,作为原子操作。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值