zoukankan      html  css  js  c++  java
  • 线程操作案例--生产者与消费者,Object类对线程的支持

    本章目标

      1)加深对线程同步的理解

      2)了解Object类中对线程的支持方法。

    实例

      生产者不断生产,消费者不断消费产品。

      生产者生产信息后将其放到一个区域中,之后消费者从区域中取出数据。

      既然生产的是信息,就可以定义一个信息的表示类,生产者和消费者同时占有信息类的引用,那么就可以将生产者和消费者两个线程通过信息类联合在一起。

      如下:

    class Info{    // 定义信息类
        private String name = "李兴华";     // 定义name属性
        private String content = "JAVA讲师"  ;        
        public void setName(String name){
            this.name = name ;
        }
        public void setContent(String content){
            this.content = content ;
        }
        public String getName(){
            return this.name ;
        }
        public String getContent(){
            return this.content ;
        }
    };

      建立生产者类,生产者实现多线程机制。

    class Producer implements Runnable{    // 通过Runnable实现多线程
        private Info info = null ;        // 保存Info引用
        public Producer(Info info){
            this.info = info ;
        }
        public void run(){
            boolean flag = false ;    // 定义标记位
            for(int i=0;i<50;i++){
                if(flag){
                    this.info.setName("李兴华") ;    // 设置名称
                    try{
                        Thread.sleep(90) ;
                    }catch(InterruptedException e){
                        e.printStackTrace() ;
                    }
                    this.info.setContent("JAVA讲师") ;    // 设置内容
                    flag = false ;
                }else{
                    this.info.setName("mldn") ;    // 设置名称
                    try{
                        Thread.sleep(90) ;
                    }catch(InterruptedException e){
                        e.printStackTrace() ;
                    }
                    this.info.setContent("www.mldnjava.cn") ;    // 设置内容
                    flag = true ;
                }
            }
        }
    };

      生产者生产50次信息,中间为了更好的发现问题,加入了延迟操作。

      实现消费者,消费者要不断取出。

    class Consumer implements Runnable{
        private Info info = null ;
        public Consumer(Info info){
            this.info = info ;
        }
        public void run(){
            for(int i=0;i<50;i++){
                try{
                    Thread.sleep(90) ;
                }catch(InterruptedException e){
                    e.printStackTrace() ;
                }
                System.out.println(this.info.getName() + 
                    " --> " + this.info.getContent()) ;
            }
        }
    };

      最后完整代码如下  

    class Info{    // 定义信息类
        private String name = "李兴华";     // 定义name属性
        private String content = "JAVA讲师"  ;        
        public void setName(String name){
            this.name = name ;
        }
        public void setContent(String content){
            this.content = content ;
        }
        public String getName(){
            return this.name ;
        }
        public String getContent(){
            return this.content ;
        }
    };
    class Producer implements Runnable{    // 通过Runnable实现多线程
        private Info info = null ;        // 保存Info引用
        public Producer(Info info){
            this.info = info ;
        }
        public void run(){
            boolean flag = false ;    // 定义标记位
            for(int i=0;i<50;i++){
                if(flag){
                    this.info.setName("李兴华") ;    // 设置名称
                    try{
                        Thread.sleep(90) ;
                    }catch(InterruptedException e){
                        e.printStackTrace() ;
                    }
                    this.info.setContent("JAVA讲师") ;    // 设置内容
                    flag = false ;
                }else{
                    this.info.setName("mldn") ;    // 设置名称
                    try{
                        Thread.sleep(90) ;
                    }catch(InterruptedException e){
                        e.printStackTrace() ;
                    }
                    this.info.setContent("www.mldnjava.cn") ;    // 设置内容
                    flag = true ;
                }
            }
        }
    };
    class Consumer implements Runnable{
        private Info info = null ;
        public Consumer(Info info){
            this.info = info ;
        }
        public void run(){
            for(int i=0;i<50;i++){
                try{
                    Thread.sleep(90) ;
                }catch(InterruptedException e){
                    e.printStackTrace() ;
                }
                System.out.println(this.info.getName() + 
                    " --> " + this.info.getContent()) ;
            }
        }
    };
    public class ThreadCaseDemo01{
        public static void main(String args[]){
            Info info = new Info();    // 实例化Info对象
            Producer pro = new Producer(info) ;    // 生产者
            Consumer con = new Consumer(info) ;    // 消费者
            new Thread(pro).start() ;
            new Thread(con).start() ;
        }
    };

      运行结果如下:

    李兴华 --> www.mldnjava.cn
    mldn --> JAVA讲师
    mldn --> JAVA讲师
    李兴华 --> www.mldnjava.cn
    mldn --> JAVA讲师
    李兴华 --> www.mldnjava.cn
    mldn --> JAVA讲师
    mldn --> JAVA讲师
    mldn --> JAVA讲师
    李兴华 --> www.mldnjava.cn
    李兴华 --> www.mldnjava.cn
    mldn --> JAVA讲师
    mldn --> www.mldnjava.cn
    李兴华 --> JAVA讲师
    李兴华 --> www.mldnjava.cn
    mldn --> JAVA讲师
    李兴华 --> www.mldnjava.cn
    mldn --> JAVA讲师
    mldn --> JAVA讲师
    mldn --> JAVA讲师
    李兴华 --> www.mldnjava.cn
    mldn --> JAVA讲师
    mldn --> www.mldnjava.cn
    李兴华 --> www.mldnjava.cn
    李兴华 --> www.mldnjava.cn
    mldn --> JAVA讲师
    李兴华 --> www.mldnjava.cn
    李兴华 --> www.mldnjava.cn
    李兴华 --> www.mldnjava.cn
    李兴华 --> www.mldnjava.cn
    mldn --> www.mldnjava.cn
    mldn --> JAVA讲师
    李兴华 --> www.mldnjava.cn
    mldn --> JAVA讲师
    李兴华 --> www.mldnjava.cn
    mldn --> JAVA讲师
    mldn --> JAVA讲师
    李兴华 --> www.mldnjava.cn
    mldn --> www.mldnjava.cn
    李兴华 --> www.mldnjava.cn
    mldn --> JAVA讲师
    李兴华 --> www.mldnjava.cn
    李兴华 --> www.mldnjava.cn
    mldn --> JAVA讲师
    李兴华 --> www.mldnjava.cn
    mldn --> JAVA讲师
    mldn --> www.mldnjava.cn
    mldn --> JAVA讲师
    李兴华 --> www.mldnjava.cn
    李兴华 --> JAVA讲师

      以上结果的红色部分可以看出,已经没有按照预先结果对应了,以上代码将之前的两个问题全部暴露出来。

      之所以会出现内容不对应情况,是因为中间加入了延迟操作,所以有可能产生不同步的问题(参考上一章)。那么可以通过同步解决设置内容的问题。

      通过同步操作修改后的内容。

    class Info{    // 定义信息类
        private String name = "李兴华";     
        private String content = "JAVA讲师"  ;        
        public synchronized void set(String name,String content){
            this.setName(name) ;    // 设置名称
            try{
                Thread.sleep(300) ;
            }catch(InterruptedException e){
                e.printStackTrace() ;
            }
            this.setContent(content) ;    // 设置内容
        }
        public synchronized void get(){
            try{
                Thread.sleep(300) ;
            }catch(InterruptedException e){
                e.printStackTrace() ;
            }
            System.out.println(this.getName() + 
                " --> " + this.getContent()) ;
        }
        public void setName(String name){
            this.name = name ;
        }
        public void setContent(String content){
            this.content = content ;
        }
        public String getName(){
            return this.name ;
        }
        public String getContent(){
            return this.content ;
        }
    };
    class Producer implements Runnable{    // 通过Runnable实现多线程
        private Info info = null ;        // 保存Info引用
        public Producer(Info info){
            this.info = info ;
        }
        public void run(){
            boolean flag = false ;    // 定义标记位
            for(int i=0;i<50;i++){
                if(flag){
                    this.info.set("李兴华","JAVA讲师") ;    // 设置名称
                    flag = false ;
                }else{
                    this.info.set("mldn","www.mldnjava.cn") ;    // 设置名称
                    flag = true ;
                }
            }
        }
    };
    class Consumer implements Runnable{
        private Info info = null ;
        public Consumer(Info info){
            this.info = info ;
        }
        public void run(){
            for(int i=0;i<50;i++){
                this.info.get() ;
            }
        }
    };
    public class ThreadCaseDemo02{
        public static void main(String args[]){
            Info info = new Info();    // 实例化Info对象
            Producer pro = new Producer(info) ;    // 生产者
            Consumer con = new Consumer(info) ;    // 消费者
            new Thread(pro).start() ;
            new Thread(con).start() ;
        }
    };

      运行结果如下:

    李兴华 --> JAVA讲师
    李兴华 --> JAVA讲师
    mldn --> www.mldnjava.cn
    mldn --> www.mldnjava.cn
    mldn --> www.mldnjava.cn
    mldn --> www.mldnjava.cn
    mldn --> www.mldnjava.cn
    mldn --> www.mldnjava.cn
    李兴华 --> JAVA讲师
    mldn --> www.mldnjava.cn
    李兴华 --> JAVA讲师
    mldn --> www.mldnjava.cn
    李兴华 --> JAVA讲师
    李兴华 --> JAVA讲师
    李兴华 --> JAVA讲师
    李兴华 --> JAVA讲师
    李兴华 --> JAVA讲师
    mldn --> www.mldnjava.cn
    mldn --> www.mldnjava.cn
    mldn --> www.mldnjava.cn
    mldn --> www.mldnjava.cn
    mldn --> www.mldnjava.cn
    mldn --> www.mldnjava.cn
    mldn --> www.mldnjava.cn
    mldn --> www.mldnjava.cn
    李兴华 --> JAVA讲师
    mldn --> www.mldnjava.cn
    李兴华 --> JAVA讲师
    李兴华 --> JAVA讲师
    李兴华 --> JAVA讲师
    李兴华 --> JAVA讲师
    李兴华 --> JAVA讲师
    mldn --> www.mldnjava.cn
    mldn --> www.mldnjava.cn
    李兴华 --> JAVA讲师
    mldn --> www.mldnjava.cn
    mldn --> www.mldnjava.cn
    李兴华 --> JAVA讲师
    李兴华 --> JAVA讲师
    李兴华 --> JAVA讲师
    李兴华 --> JAVA讲师
    李兴华 --> JAVA讲师
    李兴华 --> JAVA讲师
    李兴华 --> JAVA讲师
    mldn --> www.mldnjava.cn
    mldn --> www.mldnjava.cn
    李兴华 --> JAVA讲师
    mldn --> www.mldnjava.cn
    李兴华 --> JAVA讲师
    李兴华 --> JAVA讲师

      以上的代码解决了数据完整性问题,但是依然存在重复取的问题,既然有重复取,则肯定有重复设置的问题,

      并没有达到设置一个,取走一个的功能要求。

      如果要想采用以上这种机制,则必须依靠Object类中的方法的支持。

     Object类对线程的支持--等待与唤醒机制

      Object类是所有类的父类,此类有以下几种方法是对线程操作有所支持的。

      

      唤醒有两个方法:notify()和notifyAll()方法。

      注意,因为Object是所有类的父类,所以其他类要想使用等待和唤醒,直接使用super即可,super调用父类方法。super.wait();super.notify();

      notify()会唤醒第一个等待的线程,notifyAll()会全部唤醒,那个线程优先级高哪个先执行。

      注意wait()和notify()方法跟sleep()方法一样,需要异常处理

      

      直接修改Info类即可,增加等待与唤醒的机制。

    wait() :

    使当前线程等待直到另外一个线程调用了这个对象的Object的nofity()方法或者Object的notifyAll()方法。

    复习synchronized知识可以参考:

    java线程同步: synchronized详解(转)

    线程的同步与死锁

    可以单这里:

    Java 多线程详解(四)------生产者和消费者

    wait():执行该方法的线程对象,释放同步锁,JVM会把该线程放到等待池中,等待其他线程唤醒该线程

    notify():执行该方法的线程唤醒在等待池中等待的任意一个线程,把线程转到锁池中等待(注意锁池和等待池的区别)

    notifyAll():执行该方法的线程唤醒在等待池中等待的所有线程,把线程转到锁池中等待。

    注意:上述方法只能被同步监听锁对象来调用,这也是为啥wait() 和 notify()方法都在 Object 对象中,因为同步监听锁可以是任意对象,只不过必须是需要同步线程的共同对象即可,否则别的对象调用会报错:        java.lang.IllegalMonitorStateException

    假设 A 线程和 B 线程同时操作一个 X 对象,A,B 线程可以通过 X 对象的 wait() 和 notify() 方法来进行通信,流程如下:

    ①、当线程 A 执行 X 对象的同步方法时,A 线程持有 X 对象的 锁,B线程在 X 对象的锁池中等待

    ②、A线程在同步方法中执行 X.wait() 方法时,A线程释放 X 对象的锁,进入 X 对象的等待池中

    ③、在 X 对象的锁池中等待锁的 B 线程获得 X 对象的锁,执行 X 的另一个同步方法

    ④、B 线程在同步方法中执行 X.notify() 方法,JVM 把 A 线程从等待池中移动到 X 对象的锁池中,等待获取锁

    ⑤、B 线程执行完同步方法,释放锁,等待获取锁的 A 线程获得锁,继续执行同步方法

    package Thread1;
    class Info{    // 定义信息类
        private String name = "李兴华";  private String content = "JAVA讲师"  ;    //设置了默认值,使得消费者最开始就能消费。
      private boolean flag = false ; // 设置标志位,默认为false,使得消费者能消费,生产者一开始就进入等待,直到消费者消费完唤醒它
        public synchronized void set(String name,String content)throws InterruptedException{if(!flag){   1
                    super.wait() ;  2 //如果flag为false,该进程进入等待,不往下进行,等待消费者消费完了,执行notify才能唤醒自己,继续下面的操作
            }
            this.setName(name);  3   // 设置名称
            Thread.sleep(300) ;
            this.setContent(content) ;    // 设置内容
            flag  = false ;    // 设置为false,目的是让调用本方法的线程不会第二次执行(第二次调用的时候就会把自己wait住,直到消费者消费完了,
        才能唤醒自己,才能继续执行生产)
    super
    .notify() ;  //生产完了,唤醒正在等待消费的消费者。 } public synchronized void get()throws InterruptedException{ if(flag){ super.wait() ; } Thread.sleep(300) ; System.out.println(this.getName() + " --> " + this.getContent()) ; flag = true ; // 改变标志位,表示可以生产 super.notify() ; } public void setName(String name){ this.name = name ; } public void setContent(String content){ this.content = content ; } public String getName(){ return this.name ; } public String getContent(){ return this.content ; } }; class Producer implements Runnable{ // 通过Runnable实现多线程 private Info info = null ; // 保存Info引用 public Producer(Info info){ this.info = info ; } public void run(){ boolean flag = false ; // 定义标记位 for(int i=0;i<50;i++){ try{ if(flag){ this.info.set("李兴华","JAVA讲师") ; // 设置名称 flag = false ; }else{ this.info.set("mldn","www.mldnjava.cn") ; // 设置名称 flag = true ; } }catch(InterruptedException e)  //处理异常 { e.printStackTrace(); } } } }; class Consumer implements Runnable{ private Info info = null ; public Consumer(Info info){ this.info = info ; } public void run(){ for(int i=0;i<50;i++){ try{ this.info.get() ; }catch(InterruptedException e)  //处理异常 { e.printStackTrace(); } } } }; public class demo1{ public static void main(String args[]){ Info info = new Info(); // 实例化Info对象 Producer pro = new Producer(info) ; // 生产者 Consumer con = new Consumer(info) ; // 消费者 new Thread(pro).start() ; new Thread(con).start() ; } };

    运行结果:

    李兴华 --> JAVA讲师
    mldn --> www.mldnjava.cn
    李兴华 --> JAVA讲师
    mldn --> www.mldnjava.cn
    李兴华 --> JAVA讲师
    mldn --> www.mldnjava.cn
    李兴华 --> JAVA讲师
    mldn --> www.mldnjava.cn
    李兴华 --> JAVA讲师
    mldn --> www.mldnjava.cn
    李兴华 --> JAVA讲师
    mldn --> www.mldnjava.cn
    李兴华 --> JAVA讲师
    mldn --> www.mldnjava.cn

    分析:

    最开始设置Info对象的flag为false,使得生产者一进去就进入等待wait,消费者可以消费,此时消费者消费的是info对象默认值,消费完了设置flag为true,使得自己再次消费

    的话,自己就进入等待(wait),等待生产者生产完告诉自己(唤醒它),然后告诉生产者可以开始生产了(唤醒生产者),此时等待中的生产者唤醒,获取锁,继续往下执行,生产完了后,flag设置为false,防止自己再次相生产的时候,可以把自己进入等待状态(直到消费者消费完告诉自己(唤醒自己)),然后告诉消费者,我生产完了,你可以开始消费了(唤醒消费者),此时等待中的消费者被唤醒,开始执行消费。。。循环。

    此时等待的消费者开始继续

    总结

      1,在本程序中需要注意以下两种问题:

      1)生产者要不断生产信息,但是不能重复生产,或者生产错误信息。

      2)消费者要不断取走,但是不能重复取走。

      2,Object类对线程的支持,等待wait()和nofity()和notifyAll(),注意异常的处理。

      3,本道程序只是加深同步,等待,唤醒机制操作印象。

  • 相关阅读:
    Python爬取豆瓣电影top
    那些年我们踩的坑,依然有人在踩坑
    工行ICBC_WAPB_B2C支付接口
    SharePoint2016配置工作流开发环境
    Html+Css实现梯形选项卡
    The Ribbon Tab with id: "Ribbon.Read" has not been made available for this page or does not exist.
    SharePoint自动初始化网站列表
    常用的SharePoint命令行代码
    SharePoint开启错误提示
    Asp.Net写入读取Xml(处理文件权限)
  • 原文地址:https://www.cnblogs.com/alsf/p/5683204.html
Copyright © 2011-2022 走看看