多线程经典消费者实例

来源:互联网 发布:数据库主键约束有哪些 编辑:程序博客网 时间:2024/06/07 17:12

需求

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

设计的问题

我们的第一个问题是,如果容器满了怎么办,空了又怎么。解决的办法是使用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