黑马程序员_日记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();

时间: 2024-10-04 12:36:55

黑马程序员_日记17_Java多线程(七)的相关文章

黑马程序员_日记19_Java多线程(九)

--- android培训.java培训.期待与您交流! ---- 1 停止线程 原理:让run方法结束.而run方法中通常定义循环结构,所以就是控制住循环结构就可以了. stop方法已经过时了. 如何控制循环结构?? 1 定义结束标记 2 当线程处于了冻结状态,没有执行标记,程序一样无法结束. 这时可以循环,正常退出冻结状态,或者强制结束冻结状态. 强制结束冻结状态:interrupt();目的是线程强制从冻结状态恢复到运行状态. 但是会发生InterruptedException异常. //

黑马程序员_日记16_Java多线程(六)

--- android培训.java培训.期待与您交流! ---- 死锁 一.基本概念 死锁是什么呢? 两个线程分别持有一个资源, 并同时请求得到对方的资源. 在这种情况下产生的不正常情况就叫做死锁. 死锁在什么情况下会发生呢? 死锁在同步嵌套同步的情况下会发生. 至少有2把锁,才会产生死锁. 二.死锁实例1 首先,以前面售票的例子展示一下死锁. class Ticket implements Runnable { private int tick = 1000; Object obj = ne

黑马程序员_日记18_Java多线程(八)--生产者消费者问题JDK1.5特性

--- android培训.java培训.期待与您交流! ---- 生产者消费者问题JDK1.5特性 一.概述 在JDK1.5之前,解决生产者和消费者问题, 用的是synchronized同步+while+notify(): 但是这种方法很不安全,很容易让线程全部陷入无限等待状态. 于是我们改用notiyfyAll();来解决. 这样虽然解决了安全问题,但还是存在不足和安全隐患. notifyAll方法唤醒了线程池中全部的线程, 这并不是我们想要的! 而且,同步套同步很容易发生死锁! 在JDK1

黑马程序员-学习日记(多线程的初步认识)

 ------Java EE培训.Android培训.iOS培训.期待与您交流! ------- 进程:正在执行的应用程序.一个应用程序运行时内存分配的空间.线程:进程中一个程序执行的控制单元,一条执行路径.负责程序的执行顺序.多线程:在java虚拟机启动的时候会有一个java.exe的执行程序,即一个进程.该进程中至少有一个线程负责java程序的运行.而且这个线程运行的代码存在于main方法中. class Demo extends Thread { public void run() { f

黑马程序员-学习日记(多线程)

进程:正在执行的应用程序.一个应用程序运行时内存分配的空间.线程:进程中一个程序执行的控制单元,一条执行路径.负责程序的执行顺序. 多线程存在的意义: 程序运行中至少有两个线程在运行,一个是主函数的主线程,另一个是垃圾回收的线程. 线程创建方式一: 继承Thread类.要覆盖其run方法,调用线程的start方法. 作用:1.启动线程 2.运行run方法.目的是将自定义的代码存储在run方法中,让线程运行 cpu每次只执行一个程序,只是在快速的不同线程间切换,表现了多线程的随机性 class M

黑马程序员-学习日记(多线程安全问题和死锁认识)

------Java培训.Android培训.iOS培训.期待与您交流! ------- 安全问题产生的原因: 当多条代码语句在操作同一个共享数据时,一个线程对多条语句只执行了一部分,还没有执行完, 另一个线程参与进来执行.导致共享数据的错误. class Mlpc implements Runnable { private int ticket = 50; public void run() { while(true) { if(ticket>0) { try { Thread.sleep(2

黑马程序员_日记24_字符串获取计数练习

--- android培训.java培训.期待与您交流! ---- /* 获取一个字符串在另一个字符串中出现的次数. 例如:"kk"在"abkkcdkkefkkskk"中出现的次数 分析功能: 1 功能的结果:返回字符串1在字符串2中出现的次数------------返回值类型为int 2 有没有未知变量:有两个.第一,字符串1:第二,字符串2.--参数类型(String str1,String str2) 如何实现功能:"kk"在"a

黑马程序员-学习日记(多线程间的通信)

 ------Java EE培训.Android培训.iOS培训.期待与您交流! ------- 示例: //将资源封装成对象 class Resour { String name; String gender; } //将线程执行的任务封装成对象 class Input implements Runnable { private Resour r; Input(Resour r) { this.r = r; } public void run() { int x =0; while(true)

黑马程序员-学习日记(多线程的安全)

安全产生的原因: 当多条语句在操作同一个共享数据时,一个线程对多条语句只执行了一部分,还没有执行完, 另一个线程参与进来执行.导致共享数据的错误. class Mlpc implements Runnable { private int ticket = 50; public void run() { while(true) { if(ticket>0) { try { Thread.sleep(20); //让线程进入冻结状态 } catch (Exception e) { } System.