多线程_线程间通信

线程间通信:

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

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

 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比较照顾一点。

          

原文地址:https://www.cnblogs.com/fjfsu/p/J_FengDXC.html