zoukankan      html  css  js  c++  java
  • 多线程_线程间通信

    线程间通信:

      多个线程在处理同一资源,但是他们的任务不同(一部分线程生产鸭子,另一部分线程销售鸭子)

      从下面的代码开始,一步步的引出问题并解决

     1 public class Text2 {
     2     public static void main(String[] args) {
     3     Ziyuan r=new Ziyuan();      //创建资源
     4     Inp a=new Inp(r);             //创建输入任务
     5     Outp b=new Outp(r);          //创建输出任务
     6     Thread t=new Thread(a);        //创建输入线程,执行路径(执行输入任务)
     7     Thread t1=new Thread(b);    //创建输出线程,执行路径(执行输出任务)
     8     t.start();       //开启线程
     9     t1.start();
    10     }
    11 }
    12 class Ziyuan{
    13     String name;
    14     String sex;    
    15 }
    16 class Inp implements Runnable{
    17     Ziyuan r;
    18     int a=0;
    19     Inp(Ziyuan r){
    20         this.r=r;
    21     }
    22     public void run(){
    23         while(true){
    24             if(a==0){
    25                 r.name="黑";
    26                 r.sex="男";
    27             }else{
    28                 r.name="白白";
    29                 r.sex="女女";
    30             }
    31             a=(a+1)%2;
    32         }
    33     }    
    34 }
    35 class Outp implements Runnable{
    36     Ziyuan r;
    37     int a=0;
    38     Outp(Ziyuan r){
    39         this.r=r;
    40     }
    41     public void run(){
    42         while(true)
    43         System.out.println(r.name+"...."+r.sex);
    44     }
    45 }

    输出的结果会出现这种情况:

                  黑....女女
        白白....男

    会出现这种情况是因为有多个线程操作共享资源,并且操作共享资源的代码有多条,可以用同步解决这种安全问题

    修改后的代码为:(就是简单的加上一个锁就可以解决问题,要保证线程使用的是同一个锁)

     1 public class Text2 {
     2     public static void main(String[] args) {
     3     Ziyuan r=new Ziyuan();      //创建资源
     4     Inp a=new Inp(r);             //创建输入任务
     5     Outp b=new Outp(r);          //创建输出任务
     6     Thread t=new Thread(a);        //创建输入线程,执行路径(执行输入任务)
     7     Thread t1=new Thread(b);    //创建输出线程,执行路径(执行输出任务)
     8     t.start();       //开启线程
     9     t1.start();
    10     }
    11 }
    12 class Ziyuan{
    13     String name;
    14     String sex;    
    15 }
    16 class Inp implements Runnable{
    17     Ziyuan r;
    18     int a=0;
    19     Inp(Ziyuan r){
    20         this.r=r;
    21     }
    22     public void run(){
    23               while(true){
    24                    synchronized(r){
    25                    if(a==0){
    26                         r.name="黑";
    27                         r.sex="男";
    28                    }else{
    29                         r.name="白白";
    30                         r.sex="女女";
    31                  }
    32                  a=(a+1)%2;
    33                  }
    34               } 
    35     }    
    36 }
    37 class Outp implements Runnable{
    38     Ziyuan r;
    39     int a=0;
    40     Outp(Ziyuan r){
    41         this.r=r;
    42     }
    43     public void run(){
    44         while(true)
    45              synchronized(r){
    46                  System.out.println(r.name+"...."+r.sex);
    47              }
    48     }
    49 }

    这样虽然解决了安全问题,但是输入(输出)都是成片存在的,假设我们是在卖票,这样就会出现一张票被重复卖出多次。

    现在我就想,输入一个,打印一个。这样就引出了等待唤醒机制。

    等待唤醒机制:

      涉及的方法:

        wait()  让线程进入冻结状态, 把线程存储在线程池中

        notify()  唤醒线程池中的一个线程

        notifyall() 唤醒线程池中所有线程

        这些方法都是操做线程状态的  

        这些方法必须定义在同步中  

        这些方法必须明确属于哪个锁

                 因为这些方法是监视器的方法,同步中一把锁只能有一组监视器,锁是任意的对象,任意的对象调用的方式一定定义在Object类,所以这些方法定义在Object类中。(我的理解,锁调用这些方法去改变线程状态,要保证锁一定能调用这些方法,那就只能把这些方法定义在Object中)

     1 public class Text2 {
     2     public static void main(String[] args) {
     3     Ziyuan r=new Ziyuan();      //创建资源
     4     Inp a=new Inp(r);             //创建输入任务
     5     Outp b=new Outp(r);          //创建输出任务
     6     Thread t=new Thread(a);        //创建输入线程,执行路径(执行输入任务)
     7     Thread t1=new Thread(b);    //创建输出线程,执行路径(执行输出任务)
     8     t.start();       //开启线程
     9     t1.start();
    10     }
    11 }
    12 class Ziyuan{
    13     String name;
    14     String sex;    
    15     boolean bool=false;
    16 }
    17 class Inp implements Runnable{
    18     Ziyuan r;
    19     int a=0;
    20     Inp(Ziyuan r){
    21         this.r=r;
    22     }
    23     public void run(){
    24               while(true){
    25                    synchronized(r){
    26                       if(r.bool){
    27                           try {r.wait();} catch (InterruptedException e) {}
    28                       }
    29                    if(a==0){
    30                         r.name="黑";
    31                         r.sex="男";
    32                    }else{
    33                         r.name="白白";
    34                         r.sex="女女";
    35                  }
    36                  a=(a+1)%2;
    37                  r.bool=true;
    38                  r.notify();
    39                  }
    40               } 
    41     }    
    42 }
    43 class Outp implements Runnable{
    44     Ziyuan r;
    45     int a=0;
    46     Outp(Ziyuan r){
    47         this.r=r;
    48     }
    49     public void run(){
    50         while(true)
    51              synchronized(r){
    52                  if(!r.bool){
    53                           try {r.wait();} catch (InterruptedException e) {}
    54                       }
    55                  System.out.println(r.name+"...."+r.sex);
    56                  r.bool=false;
    57                  r.notify(); 
    58              }
    59     }
    60 }

    这样问题便得到了解决

    可是我不满足于现在的单线程输入 单线程输出;搞点猛地多线程输入 多线程输出。引出经典操作“多生产者多消费者”

    多生产者多消费者:

      在上面的代码基础上简单的修改成买卖鸭子,并实现多生产者多消费者,看看会出什么问题:

     1 public class Text2 {
     2     public static void main(String[] args) {
     3     Ziyuan r=new Ziyuan();      //创建资源
     4     Inp a=new Inp(r);             //创建输入任务
     5     Outp b=new Outp(r);          //创建输出任务
     6     Thread t0=new Thread(a);        //创建输入线程,执行路径(执行输入任务)
     7     Thread t1=new Thread(a);    //创建输出线程,执行路径(执行输出任务)
     8     Thread t2=new Thread(b);
     9     Thread t3=new Thread(b);
    10     t0.start();       //开启线程
    11     t1.start();
    12     t2.start();
    13     t3.start();
    14     }
    15 }
    16 class Ziyuan{
    17     boolean bool=false;
    18     int a=0;
    19 }
    20 class Inp implements Runnable{
    21     Ziyuan r;
    22     Inp(Ziyuan r){
    23         this.r=r;
    24     }
    25     public void run(){
    26               while(true){
    27                    synchronized(r){
    28                       if(r.bool){
    29                           try {r.wait();} catch (InterruptedException e) {}
    30                       }
    31                       r.a++;
    32                       System.out.println(Thread.currentThread().getName()+"生产鸭子。。。"+r.a);
    33                  r.bool=true;
    34                  r.notify();
    35                  }
    36               } 
    37     }    
    38 }
    39 class Outp implements Runnable{
    40     Ziyuan r;
    41     Outp(Ziyuan r){
    42         this.r=r;
    43     }
    44     public void run(){
    45         while(true)
    46              synchronized(r){
    47                 if(!r.bool){
    48                           try {r.wait();} catch (InterruptedException e) {}
    49                       }
    50                  System.out.println(Thread.currentThread().getName()+"卖出鸭子"+r.a--);
    51                  r.bool=false;
    52                  r.notify(); 
    53              }
    54     }
    55 }

      28 47 行使用的是if判断,这就表示如果线程0在生产了鸭子后,冻结在28行处的1线程醒了,那么他不会在判断,而是继续生产鸭子,这就导致错误数据的出现(这时将if换成while)。

      换成了while后发现,出现了死锁情况(原因:比如.线程1.2.3已经进入了冻结状态,线程0进冻结前,唤醒了t1线程,然后t1获得了CPU的执行权,在t1进行了判断后也进入了冻结状态,这是所有的线程都进入了冻结状态)。

      只要我们保证一定能够唤醒对方线程,那么就能解决问题,所以使用notifyAll()方法替换notify()方法就可以解决问题。

      注意:1.if只有一次判断,会导致不该运行的线程运行  2.while判断标记,解决了线程获得执行权之后是否要运行的问题,如果while和notify一起使用会导致死锁  3.notifyAll解决了一定会唤醒对方线程的问题(如果己方线程总是获得CPU执行权,那么效率会降低)

    Lock接口和Condition接口

      如果只是为了唤醒对方线程,而去唤醒所有线程,很明显是不明智的,所以在JDK1.5版本后为我们提供了解决方法

      Lock:  lock()获取锁  unlock()释放锁  ReentrantLock(已知实现子类)  Lock代替了synchronized方法和语句的使用

      Condition: await()冻结线程  signal()唤醒一个线程  signalAll()唤醒所有线程  Condition代替了Objcet监视器方法的使用

      同步中,一把锁只能有一组监视器,但是Lock锁已有多组Condition监视器

      

      如果线程发生了异常,那么锁将无法释放,所以unlock()一定要放在finally中

    使用升级后的JDK所提供的方法重新写代码,提高效率

     1 import java.util.concurrent.locks.Condition;
     2 import java.util.concurrent.locks.Lock;
     3 import java.util.concurrent.locks.ReentrantLock;
     4 
     5 
     6 public class Text2 {
     7     public static void main(String[] args) {
     8         Ziyuan r=new Ziyuan();
     9         Inp in=new Inp(r);
    10         Outp out=new Outp(r);
    11         Thread t0=new Thread(in);
    12         Thread t1=new Thread(in);
    13         Thread t2=new Thread(out);
    14         Thread t3=new Thread(out);
    15         t0.start();
    16         t1.start();
    17         t2.start();
    18         t3.start();
    19     
    20     }
    21 }
    22 class Ziyuan{
    23     private String name;
    24     private String sex;
    25     private boolean bool=false;
    26     private int mun=1;
    27     Lock suo=new ReentrantLock();
    28     Condition inJian=suo.newCondition();
    29     Condition outJian=suo.newCondition();
    30     
    31     void show(String name){
    32         try{
    33             suo.lock();
    34             while(bool){
    35                 try {inJian.await();} catch (InterruptedException e) {}
    36             }
    37             this.name=name+mun++;
    38             System.out.println(Thread.currentThread().getName()+"生产。。。"+this.name);
    39             bool=true;
    40             outJian.signal();
    41         }finally{
    42             suo.unlock();
    43         }
    44         
    45     }
    46     void show1(){
    47         try{
    48             suo.lock();
    49             while(!bool){
    50                 try {outJian.await();} catch (InterruptedException e) {}
    51             }
    52             System.out.println(Thread.currentThread().getName()+"消费.................."+name);
    53             bool=false;
    54             inJian.signal();
    55         }finally{
    56             suo.unlock();
    57         }
    58         
    59     }
    60 }
    61 class Inp implements Runnable{
    62     Ziyuan r;
    63     Inp(Ziyuan r){
    64         this.r=r;
    65     }
    66     public void run(){
    67            while(true){
    68                r.show("烤鸭");
    69               } 
    70     }    
    71 }
    72 class Outp implements Runnable{
    73     Ziyuan r;
    74     Outp(Ziyuan r){
    75         this.r=r;
    76     }
    77     public void run(){
    78         while(true){
    79             r.show1();
    80         }
    81     }
    82 }

    wait和sleep的区别:

      1:wait可以指定时间,也可以不指定

       sleep一定要指定时间

      2:wait释放执行权,释放锁

       sleep释放执行权,不释放锁

    停止线程:

      stop() 过时了,存在安全隐患

      run() 方法结束(run方法运行完了,该线程会自动结束)(使用标记结束run方法),代码如下:

     1 class Producer implements Runnable
     2 {
     3     int a=0;
     4     boolean bool=true; //标记
     5     public void run()
     6     {
     7         while(bool){
     8             System.out.println(Thread.currentThread().getName()+"          "+a++);
     9         }
    10     }
    11     public void set(boolean bool){   //修改标记,用来控制run()方法的结束
    12         this.bool=bool;
    13     }
    14     
    15 }
    16 class  ProducerConsumerDemo2
    17 {
    18     public static void main(String[] args) 
    19     {
    20         Producer pro = new Producer();
    21         Thread t0 = new Thread(pro);
    22         t0.start();
    23         for(int i=0;i<500;i++){
    24             System.out.println(Thread.currentThread().getName()+".................."+i);
    25             if(i==499){
    26                 pro.set(false);
    27             }
    28             
    29         }
    30         System.out.println(Thread.currentThread().getName()+"..................................."+"over");
    31     }
    32 }

    如果线程处于冻结状态,那么利用标记来结束线程是行不通的;所以,Thread提供了interrupt()方法强制唤醒线程(因此会抛异常)

     1 class Producer implements Runnable
     2 {
     3     int a=0;
     4     boolean bool=true; //标记
     5     public synchronized void run()
     6     {
     7         while(bool){
     8             try {
     9                 wait();
    10             } 
    11             catch (InterruptedException e) {
    12                 bool=false;  //因为这中唤醒机制会发生InterruptedException异常,一定会运行catch 所以一般吧标记写在catch中
    13             }
    14             System.out.println(Thread.currentThread().getName()+"          "+a++);
    15         }
    16     }    
    17 }
    18 class  ProducerConsumerDemo2
    19 {
    20     public static void main(String[] args) 
    21     {
    22         Producer pro = new Producer();
    23         Thread t0 = new Thread(pro);
    24         t0.start();
    25         for(int i=0;i<500;i++){
    26             System.out.println(Thread.currentThread().getName()+".................."+i);
    27             if(i==499){
    28                 t0.interrupt();
    29             }
    30             
    31         }
    32         System.out.println(Thread.currentThread().getName()+"..................................."+"over");
    33     }
    34 }

    守护线程(后台线程):

      steDaemon(boolean)    (当参数为true的时候这个线程为守护线程)  

                  后台和前台线程基本一样,唯一不同的就是,当前台线程全部运行结束后,后台线程也会跟着结束。(当正在运行的程序全都为守护线程的时候,JAVA虚拟机会自动退出)

     其他方法:

       join() 一般临时加入一个线程执行运算,会调用这个方法(假设有t0 t1 main三个线程,如果t0调用了这个方法,那么主线程会冻结,t0 t1抢夺CPU的执行权,当t0运算完成,main才接触冻结状态)

       setPriority(int)  设置线程的优先级(1-10)一般分为三个等级(1-5-10)注意并不是设置成了10这个最高级,这个线程就会拼命运行,只不过是CPU比较照顾一点。

              

  • 相关阅读:
    springboot Filter中无法注入Bean对象的解决办法
    springboot 2.x 采用监控模块
    Spring Cloud Alibaba项目构建(一)
    spring boot完成图片上传下载的功能
    scrapy初探
    RESETFUL四种方式提交区别
    qt TCP UDP-多线程笔记
    [‘1‘,‘2‘,‘3‘].map(parseInt)结果讲解
    安装nprogress进度条插件
    vue项目中扫二维码跳转页面---前端实现过程
  • 原文地址:https://www.cnblogs.com/fjfsu/p/J_FengDXC.html
Copyright © 2011-2022 走看看