黑马程序员_日记17_Java多线程(七)

 ——- android培训java培训、期待与您交流! ———-

线程间通信

线程间的通信:
其实就是多个线程操作同一个资源,但是操作的动作不同。

一、我的示例

这是我写的关于线程间通信的示例。

本示例需要定义三个类:
1 资源类Resource
2 输入类Input
3 输出类Output

步骤:
1 先写出基本代码
2 找安全问题并修改

1 初级代码

//本类定义的资源是,人的数据
class Resource
{
    private String name;//私有化人的名字
    private String sex;//私有化人的性别

    //提供set方法,来访问name和sex
    public void set(String name,String sex)
    {
        this.name = name;
        this.sex = sex;
    }

    //提供get方法来打印name和sex
    public void get()
    {
        System.out.println(name+"......"+sex);
    }
}

//本类定义的是往共享数据中存数据
class Input implements Runnable
{
    //私有化共享数据
    private Resource r;

    //利用重载构造函数来传递共享数据
    Input(Resource r)
    {
        this.r = r;
    }

    //首先覆盖run方法,定义功能:一种情况存入男人,另一种情况存入女人
    public void run()
    {
        //定义一个布尔变量来区分两种情况
        boolean flag_in = true;

        while(true)
        {
                if(flag_in)//如果为真,则存入男,张三
                {
                    r.set("张三","男");
                    flag_in = false;
                }
                else//如果为假,则存入女,李丽
                {
                    r.set("李丽",".........女");
                    flag_in = true;
                }
        }
    }
}

//本类定义的是从共享数据中取数据
class Output implements Runnable
{
    private Resource r;

    Output(Resource r)
    {
        this.r = r;
    }

    public void run()
    {
        while(true)
            r.get();
    }
}



class MyInputOutputDemo 
{
    public static void main(String[] args) 
    {
        //建立资源对象
        Resource r = new Resource();

        //建立实现了runnable接口的对象
        Input in = new Input(r);
        Output out = new Output(r);

        //建立线程对象
        Thread t1 = new Thread(in);//输入线程
        Thread t2 = new Thread(out);//输出线程

        //启动线程
        t1.start();
        t2.start();
    }
}

运行结果中存在:
李丽……男
张三……………女
说明了线程通信存在安全问题
什么原因呢??
因为在存数据的同时,还可以取数据。

2 修正代码

下面利用上次所学的解决安全问题的办法

//本类定义的资源是,人的数据
class Resource
{
    private String name;//私有化人的名字
    private String sex;//私有化人的性别
    private boolean flag = false;//设置监视器来控制输入输出

    //提供set方法,来访问name和sex
    public void set(String name,String sex)
    {
        if(!flag)//监视器为假的时候,存入数据
        {
            this.name = name;
            this.sex = sex;
            flag = true;
        }
    }

    //提供get方法来打印name和sex
    public void get()
    {
        if(flag)//监视器为真的时候取出数据
        {
            System.out.println(name+"......"+sex);
            flag = false;
        }
    }
}

//本类定义的是往共享数据中存数据
class Input implements Runnable
{
    //私有化共享数据
    private Resource r;

    //利用重载构造函数来传递共享数据
    Input(Resource r)
    {
        this.r = r;
    }

    //首先覆盖run方法,定义功能:一种情况存入男人,另一种情况存入女人
    public void run()
    {
        //定义一个布尔变量来区分两种情况
        boolean flag_in = true;

        while(true)
        {
                if(flag_in)//如果为真,则存入男,张三
                {
                    r.set("张三","男");
                    flag_in = false;
                }
                else//如果为假,则存入女,李丽
                {
                    r.set("李丽",".........女");
                    flag_in = true;
                }
        }
    }
}

//本类定义的是从共享数据中取数据
class Output implements Runnable
{
    private Resource r;

    Output(Resource r)
    {
        this.r = r;
    }

    public void run()
    {
        while(true)
            r.get();
    }
}



class MyInputOutputDemo 
{
    public static void main(String[] args) 
    {
        //建立资源对象
        Resource r = new Resource();

        //建立实习了runnable接口的对象
        Input in = new Input(r);
        Output out = new Output(r);

        //建立线程对象
        Thread t1 = new Thread(in);//输入线程
        Thread t2 = new Thread(out);//输出线程

        //启动线程
        t1.start();
        t2.start();
    }
}

运行结果显示:
通过给资源增加监视器,已经解决了存取错误的一个问题。

但是还存在这样的问题:
张三……男
张三……男
张三……男
李丽……………女

这说明还存在安全问题。
这是由于CPU切换造成的。

正确的情况应该是交替输出:
存一个取一个。

3 等待唤醒机智解决安全问题的代码

那么该怎么解决呢???
Java提供了等待唤醒机制。
下面改动一下程序。

//本类定义的资源是,人的数据
class Resource
{
    private String name;//私有化人的名字
    private String sex;//私有化人的性别
    private boolean flag = false;//设置监视器来控制输入输出

    //提供set方法,来访问name和sex
    public synchronized void set(String name,String sex)
    {
        if(flag)//监视器为真的时候,等待
        {
            try
            {
                this.wait();
            }
            catch (Exception e)
            {
            }
        }

            this.name = name;
            this.sex = sex;
            flag = true;
            this.notify();//唤醒线程池中的第一个线程
    }

    //提供get方法来打印name和sex
    public synchronized void get()
    {
        if(!flag)//监视器为假的时候,线程等待
        {
            try
            {
                this.wait();
            }
            catch (Exception e)
            {
            }
        }
                System.out.println(name+"......"+sex);
                flag = false;
                this.notify();//唤醒线程池中的第一个线程
    }
}

//本类定义的是往共享数据中存数据
class Input implements Runnable
{
    //私有化共享数据
    private Resource r;

    //利用重载构造函数来传递共享数据
    Input(Resource r)
    {
        this.r = r;
    }

    //首先覆盖run方法,定义功能:一种情况存入男人,另一种情况存入女人
    public void run()
    {
        //定义一个布尔变量来区分两种情况
        boolean flag_in = true;

        while(true)
        {
                if(flag_in)//如果为真,则存入男,张三
                {
                    r.set("张三","男");
                    flag_in = false;
                }
                else//如果为假,则存入女,李丽
                {
                    r.set("李丽",".........女");
                    flag_in = true;
                }
        }
    }
}

//本类定义的是从共享数据中取数据
class Output implements Runnable
{
    private Resource r;

    Output(Resource r)
    {
        this.r = r;
    }

    public void run()
    {
        while(true)
            r.get();
    }
}



class MyInputOutputDemo 
{
    public static void main(String[] args) 
    {
        //建立资源对象
        Resource r = new Resource();
        /*
        //建立实现了runnable接口的对象
        Input in = new Input(r);
        Output out = new Output(r);

        //建立线程对象
        Thread t1 = new Thread(in);//输入线程
        Thread t2 = new Thread(out);//输出线程

        //启动线程
        t1.start();
        t2.start();
        */

        //上面的6行代码啊可以简化为2行
        new Thread(new Input(r)).start();
        new Thread(new Output(r)).start();

    }
}

运行结果显示成功解决输入线程和输出线程通信的安全问题。

二、生产者消费者问题

生产者消费者问题

一、先处理只有1个生产者和1个消费者的情况

二、再处理2个生产者和2个消费者的情况

对于一、
步骤:
1 建立一个资源类,包含成员变量(商品名称,商品编号、监视器)
和方法(设置商品名称和商品编号)

2 建立生产者类,实现Runnable接口,覆盖run方法

3 建立消费者类,实现Runnable接口,覆盖run方法

① 1个消费者线程和1个生产者线程安全运行的代码

//1个消费者线程和1个生产者线程
class Resource
{
    private String name;//商品名称
    private int count=1;//商品编号
    private boolean flag = false;//监视器

    //设置商品名称,编号自动加1
    public synchronized void set(String name)
    {
        if(flag)//如果资源监视器为真,那么调用本方法的对象等待
            try
            {
                this.wait();
            }
            catch (Exception e)
            {
            }
                this.name = name;
                 count++;
                 System.out.println(Thread.currentThread().getName()+"..."+name+"...生产者...编号:"+count);
                flag = true;
                this.notify();//唤醒线程池中的第一个线程
    }

    //获取商品名称和编号
    public synchronized void get()
    {
        if(!flag)//如果资源监视器为假,那么调用本方法的对象等待
        try
        {
            this.wait();
        }
        catch (Exception e)
        {
        }
            System.out.println(Thread.currentThread().getName()+"..."+name+"....消费者.........编号:"+count);
            flag = false;
            this.notify();//唤醒线程池中的第一线程
    }

}

class Producer implements Runnable
{
    private Resource r;

    //利用传递引用类型变量和构造函数避免造成初始化对象不唯一
    Producer(Resource r)
    {
        this.r =r;
    }

    public void run()
    {
        while(true)
            r.set("商品");
    }

}

class Consumer implements Runnable
{
    private Resource r;

    Consumer(Resource r)
    {
        this.r = r;
    }

    public void run()
    {
        while(true)
            r.get();
    }
}






class MyProducerConsumerDemo 
{
    public static void main(String[] args) 
    {
        //建立资源对象
        Resource r =new Resource();

        //建立生产者对象
        Producer p =new Producer(r);

        //建立消费者对象
        Consumer c = new Consumer(r);

        //建立生产者线程
        Thread tp1 = new Thread(p);

        //建立消费者线程
        Thread cp1 = new Thread(c);

        //启动生产者线程
        tp1.start();

        //启动消费者线程
        cp1.start();


        //简化代码
        //new Thread(new Producer(r)).start();
        //new Thread(new Consumer(r)).start();

    }
}

② 2个生产者和2个消费这正常运行的代码

//两个生产者和两个消费者线程
class Resource
{
    private String name;//商品名称
    private int count=1;//商品编号
    private boolean flag = false;//监视器

    //设置商品名称,编号自动加1
    public synchronized void set(String name)
    {
        //if(flag)//如果资源监视器为真,那么调用本方法的对象等待
        while(flag)
            try
            {
                this.wait();
            }
            catch (Exception e)
            {
            }
                this.name = name;
                 count++;
                 System.out.println(Thread.currentThread().getName()+"..."+name+"...生产者...编号:"+count);
                flag = true;
                this.notifyAll();//唤醒线程池中的第一个线程
    }

    //获取商品名称和编号
    public synchronized void get()
    {

        //if(!flag)//如果资源监视器为假,那么调用本方法的对象等待
        while(!flag)
        try
        {
            this.wait();
        }
        catch (Exception e)
        {
        }
            System.out.println(Thread.currentThread().getName()+"..."+name+"....消费者.........编号:"+count);
            flag = false;
            this.notifyAll();//唤醒线程池中的第一线程
    }

}

class Producer implements Runnable
{
    private Resource r;

    //利用传递引用类型变量和构造函数避免造成初始化对象不唯一
    Producer(Resource r)
    {
        this.r =r;
    }

    public void run()
    {
        while(true)
            r.set("商品");
    }

}

class Consumer implements Runnable
{
    private Resource r;

    Consumer(Resource r)
    {
        this.r = r;
    }

    public void run()
    {
        while(true)
            r.get();
    }
}






class MyProducerConsumerDemo 
{
    public static void main(String[] args) 
    {
        //建立资源对象
        Resource r =new Resource();

        //建立生产者对象
        Producer p =new Producer(r);

        //建立消费者对象
        Consumer c = new Consumer(r);

        //建立生产者线程
        Thread tp1 = new Thread(p);
        Thread tp2 = new Thread(p);

        //建立消费者线程
        Thread cp1 = new Thread(c);
        Thread cp2 = new Thread(c);

        //启动生产者线程
        tp1.start();
        tp2.start();

        //启动消费者线程
        cp1.start();
        cp2.start();
    }
}

运行结果出现:
生产一次,消费2次的情况。
Thread-1…商品…生产者…编号:359
Thread-3…商品….消费者………编号:359
Thread-2…商品….消费者………编号:359

生产2次,消费1次的情况。
Thread-1…商品…生产者…编号:3424
Thread-0…商品…生产者…编号:3425
Thread-3…商品….消费者………编号:3425

分析和总结

这说明,原来在1个生产者和1个消费者安全
但在2个生产者和2个消费者就不安全了。

为什么会这样呢??
因为在用if语句判断的时候,当线程被唤醒后,它并没有再判断flag,
而是直接执行下一条语句,所以我们可以把if改成while。

if(flag)//如果资源监视器为真,那么调用本方法的对象等待

改成while(flag)的时候,运行结果为:
Thread-0…商品…生产者…编号:2
Thread-3…商品….消费者………编号:2
Thread-1…商品…生产者…编号:3
Thread-3…商品….消费者………编号:3
Thread-0…商品…生产者…编号:4

如图所示:
这里写图片描述
4个线程全部处于睡眠状态,不再打印。

这又是因为睡眠呢??
原因是notify唤醒的都是线程池中的第一个线程,
这就导致了被唤醒的线程不一定是对方的线程。
而当唤醒的是本方线程时候,线程就全部陷入了等待。
该怎么解决呢??
可以用notifyAll();

运行结果显示安全问题被解决!

小结:
在多个生产者和多个消费者的情况下,
用while+notifyAll();

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值