线程间的通讯

Posted liclblog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程间的通讯相关的知识,希望对你有一定的参考价值。

  

  直接上一个Demo,模拟生产者和消费者,就是生产了一个物品,然后消费一个物品,就这样你一下,我一下,让他们有类似有通讯的功能一样知道彼此生产了而且消费了。这里需要考虑两个问题面向这个demo时候,第一是线程的安全问题,第二是通讯问题,你一下,我一下

 1 package com.thread;
 2 // 定义一个共享的数据
 3 class User {
 4     public String name;
 5     public String age;
 6     boolean flag = true;  // 稍后会用到
 7 }
 8 
 9 // 模拟生产者
10 class Input extends Thread {
11     // 把共享变量传递当生产者中
12     public User u;
13     Input(User u) {
14         this.u = u;
15     }
16     int count = 1;// count只是为了逻辑控制
17     
18     @Override
19     public void run() {
20 
21         while (true) {
22             if (count==0) {
23                 u.name = "剑二十三";
24                 u.age = "999";
25             } else {
26                 u.name = "XXX";
27                 u.age = "123";
28             }
29             count = (count+1)%2;// 逻辑控制count等于0 和不等于0
30          
31         }
32 
33     }
34 }
35 
36 class Out extends Thread {
37 // 共享变量传入消费者中
38     public User u;
39     Out(User u) {
40         this.u = u;
41     }
42 
43     @Override
44     public void run() {
45         while (true) {
46          47                 System.out.println(u.name + "----" + u.age);
48 49            
50         }
51     }
52 }
53 
54 // 模拟生产者和消费者,线程间的通讯
55 public class ThreadDemo4 {
56     public static void main(String[] args) {
57         // 创建共享变量,并创建两个线程,把共享变量出入其中。开启两个线程。
58         // 一般理想状态是为了打印         剑二十三+999     与     XXX+123   两种字符串  并且 每次你输出一下我输出一下。 
59         // 实际代码打印的确是    剑二十三+123  或者     XXX+999  并且还不是 一行一个样,你输出一下我输出一下。
60         User u = new User();
61         Input i = new Input(u);
62         Out o = new Out(u);
63         i.start();
64         o.start();
65 //         部分打印结果
66 //        剑二十三----123
67 //        剑二十三----999
68 //        XXX----999
69 //        XXX----999
70 //        XXX----123
71         
72     }
73 
74 }

产生这种结果的原因:

  1.因为线程之间出现了并发情况,在赋值的过程中,可能刚赋值完  ‘剑二十三’  还有没赋值  ‘999’ 的信息,就打印了,所以打印的是上次保存共享变量的结果。

  2.线程之间没有进行通讯,通过打印答案可以知道,并没有你生产一个我消费一个,你才能在生产一个,我在消费一个。

解决:

  首先解决线程安全问题,我们首先想到synchronized 那么我们就用 synchronized 先来实现线程的安全问题,在解决通讯问题。

 1 package com.thread;
 2 
 3 // 定义一个共享的数据
 4 class User {
 5     public String name;
 6     public String age;
 7     boolean flag = true;  // 稍后通讯时候可能会用到
 8 }
 9 
10 // 模拟生产者
11 class Input extends Thread {
12     // 把共享变量传递当生产者中
13     public User u;
14 
15     Input(User u) {
16         this.u = u;
17     }
18 
19     int count = 1;// count只是为了逻辑控制
20 
21     @Override
22     public void run() {
23 
24         while (true) {
25             synchronized (u) {    // 因为要用同一把锁,选定为对象锁 u
26                 if (count == 0) {
27                     u.name = "剑二十三";
28                     u.age = "999";
29                 } else {
30                     u.name = "XXX";
31                     u.age = "123";
32                 }
33                 count = (count + 1) % 2;// 逻辑控制count等于0 和不等于0
34                 
35             }
36 
37         }
38 
39     }
40 }
41 
42 class Out extends Thread {
43     // 共享变量传入消费者中
44     public User u;
45 
46     Out(User u) {
47         this.u = u;
48     }
49 
50     @Override
51     public void run() {
52         while (true) {
53             synchronized (u) {    // 因为要用同一把锁,选定为对象锁 u
54               55                     System.out.println(u.name + "----" + u.age);
56 57                 
58             }
59         }
60     }
61 }
62 
63 // 模拟生产者和消费者,线程间的通讯
64 public class ThreadDemo4 {
65     public static void main(String[] args) {
66         // 创建共享变量,并创建两个线程,把共享变量出入其中。开启两个线程。
67         // 实现了线程之间的安全问题,但是并没有实现通讯
68         User u = new User();
69         Input i = new Input(u);
70         Out o = new Out(u);
71         i.start();
72         o.start();
73         // 部分打印结果
74 //        剑二十三----999
75 //        XXX----123
76 //        剑二十三----999
77 //        XXX----123
78 //        XXX----123
79 //        剑二十三----999
80     }
81 
82 }

到了这步,已经解决了线程的安全问题。但是通讯问题没有得到解决。解决通讯问题有几个方法 wait(), notify() ,notifyAll() 

wait()notifynotifyAll()方法

wait()notify()notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态。

这三个方法最终调用的都是jvm级的native方法。随着jvm运行平台的不同可能有些许差异。

1.如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。

2.如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。

3.如果对象调用了notifyAll方法就会通知所有等待这个对象控制权的线程继续运行。

 

注意:一定要在线程同步中使用,并且是同一个锁的资源

 

  1 package com.thread;
  2 
  3 // 定义一个共享的数据
  4 class User {
  5     public String name;
  6     public String age;
  7     boolean flag = true;
  8 }
  9 
 10 // 模拟生产者
 11 class Input extends Thread {
 12     // 把共享变量传递当生产者中
 13     public User u;
 14 
 15     Input(User u) {
 16         this.u = u;
 17     }
 18 
 19     int count = 1;// count只是为了逻辑控制
 20 
 21     @Override
 22     public void run() {
 23 
 24         while (true) {
 25             synchronized (u) { // 因为要用同一把锁,选定为对象锁 u
 26 
 27                 if (!u.flag) {
 28                     try {
 29                         u.wait();
 30                     } catch (InterruptedException e) {
 31                         e.printStackTrace();
 32                     }
 33                 }
 34 
 35                 if (count == 0) {
 36                     u.name = "剑二十三";
 37                     u.age = "999";
 38                 } else {
 39                     u.name = "XXX";
 40                     u.age = "123";
 41                 }
 42                 count = (count + 1) % 2;// 逻辑控制count等于0 和不等于0
 43                 u.flag = false; // 把共享flag变为false
 44                 u.notify();
 45             }
 46 
 47         }
 48 
 49     }
 50 }
 51 
 52 class Out extends Thread {
 53     // 共享变量传入消费者中
 54     public User u;
 55 
 56     Out(User u) {
 57         this.u = u;
 58     }
 59 
 60     @Override
 61     public void run() {
 62         while (true) {
 63             synchronized (u) { // 因为要用同一把锁,选定为对象锁 u
 64                 if (u.flag) {
 65                     try {
 66                         u.wait();
 67                     } catch (InterruptedException e) {
 68                         e.printStackTrace();
 69                     }
 70 
 71                     System.out.println(u.name + "----" + u.age);
 72                 }
 73                 u.flag = true; // 把共享变量flag变为true
 74                 u.notify();
 75             }
 76         }
 77     }
 78 }
 79 
 80 // 模拟生产者和消费者,线程间的通讯
 81 public class ThreadDemo4 {
 82     public static void main(String[] args) {
 83         // 创建共享变量,并创建两个线程,把共享变量出入其中。开启两个线程。
 84         // 实现了通讯
 85         User u = new User();
 86         Input i = new Input(u);
 87         Out o = new Out(u);
 88         i.start();
 89         o.start();
 90         // 部分打印结果
 91 //        剑二十三----999
 92 //        XXX----123
 93 //        剑二十三----999
 94 //        XXX----123
 95 //        剑二十三----999
 96 //        XXX----123
 97 //        剑二十三----999
 98 //        XXX----123
 99     }
100 
101 }

 

wait() 把持有该对象的线程控制权交出去!一定要充分理解这句话,只有交出去,第二个线程传过来的 notify() 才能够解锁它,所以他们两个互相解锁唤醒,就实现了通讯的功能。

并且不能单独使用必须在线程同步中使用!!!否则就会报错

 

waitsleep区别?

  对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。

sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。

在调用sleep()方法的过程中,线程不会释放对象锁。

而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备

 

获取对象锁进入运行状态。

第二种同步锁的功能 Lock锁 

Lock 接口与 synchronized 关键字的区别

Lock 接口可以尝试非阻塞地获取锁 当前线程尝试获取锁。如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁。
Lock
接口能被中断地获取锁 与 synchronized 不同,获取到锁的线程能够响应中断,当获取到的锁的线程被中断时,中断异常将会被抛出,同时锁会被释放。

Lock 接口在指定的截止时间之前获取锁,如果截止时间到了依旧无法获取锁,则返回。

Lock写法

 

Lock lock  = new ReentrantLock();

lock.lock();

try{

//可能会出现线程安全的操作

}finally{

//一定在finally中释放锁

//也不能把获取锁在try中进行,因为有可能在获取锁的时候抛出异常

  lock.unlock();

}

 

 

 

package com.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class User {
    public String name;
    public String age;
    boolean flag = true;
    // 创建锁
    Lock lock = new ReentrantLock();
}
// 模拟生产者
class Input extends Thread {
    // 把共享变量传递当生产者中
    public User u;
    Input(User u) {
        this.u = u;
    }
    int count = 1;// count只是为了逻辑控制

    @Override
    public void run() {

        while (true) {

            u.lock.lock();    // 上锁,逻辑代码用try起来,可能会抛出异常,所以汉子型finally解锁
            if (!u.flag) {

                try {
                    if (count == 0) {
                        u.name = "剑二十三";
                        u.age = "999";
                    } else {
                        u.name = "XXX";
                        u.age = "123";
                    }
                    count = (count + 1) % 2;// 逻辑控制count等于0 和不等于0
                    u.flag = false; // 把共享flag变为false
                    // u.notify();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    u.lock.unlock();
                }
            }
        }

    }
}

class Out extends Thread {
    // 共享变量传入消费者中
    public User u;

    Out(User u) {
        this.u = u;
    }

    @Override
    public void run() {
        while (true) {
            u.lock.lock();// 上锁,逻辑代码用try起来,可能会抛出异常,所以汉子型finally解锁
            try {
                if (u.flag) {
                    System.out.println(u.name + "----" + u.age);
                    u.flag = true; // 把共享变量flag变为true
                }
            } catch (Exception e) {
            } finally {
                u.lock.unlock();
            }
        }
    }
}

// 模拟生产者和消费者,线程间的通讯
public class ThreadDemo4 {
    public static void main(String[] args) {
        User u = new User();
        Input i = new Input(u);
        Out o = new Out(u);
        i.start();
        o.start();
    }

}

但是这次的打印结果又不一样了,虽然实现了高效率的锁,但是通讯没了,打印结果是程序在运行,没用打印记录。这时候需要使用 Condition

用Condition 代替 nodity 和 wait

Condition用法

 Condition的功能类似于在传统的线程技术中的,Object.wait()Object.notify()的功能

代码

 

Condition condition = lock.newCondition();

res. condition.await();  类似wait

res. Condition. Signal() 类似notify

 

  

  1 package com.lock;
  2 
  3 import java.util.concurrent.locks.Condition;
  4 import java.util.concurrent.locks.Lock;
  5 import java.util.concurrent.locks.ReentrantLock;
  6 
  7 class User {
  8     public String name;
  9     public String age;
 10     boolean flag = true;
 11     // 创建锁
 12     Lock lock = new ReentrantLock();
 13 }
 14 
 15 // 模拟生产者
 16 class Input extends Thread {
 17     // 把共享变量传递当生产者中
 18     public User u;
 19     Condition condition;
 20 
 21     Input(User u, Condition condition) {
 22         this.condition = condition;
 23         this.u = u;
 24     }
 25 
 26     int count = 1;// count只是为了逻辑控制
 27 
 28     @Override
 29     public void run() {
 30 
 31         while (true) {
 32             try {
 33                 u.lock.lock(); // 上锁,逻辑代码用try起来,可能会抛出异常,所以汉子型finally解锁
 34                 if (!u.flag) {
 35                     condition.await();
 36                 }
 37                 if (count == 0) {
 38                     u.name = "剑二十三";
 39                     u.age = "999";
 40                 } else {
 41                     u.name = "XXX";
 42                     u.age = "123";
 43                 }
 44                 count = (count + 1) % 2;// 逻辑控制count等于0 和不等于0
 45                 u.flag = false; // 把共享flag变为false
 46                 condition.signal();
 47             } catch (Exception e) {
 48             } finally {
 49                 u.lock.unlock();
 50             }
 51 
 52         }
 53 
 54     }
 55 }
 56 
 57 class Out extends Thread {
 58     // 共享变量传入消费者中
 59     public User u;
 60     Condition condition;
 61 
 62     Out(User u, Condition condition) {
 63         this.condition = condition;
 64         this.u = u;
 65     }
 66 
 67     @Override
 68     public void run() {
 69         while (true) {
 70             try {
 71                 u.lock.lock();// 上锁,逻辑代码用try起来,可能会抛出异常,所以汉子型finally解锁
 72 
 73                 if (u.flag) {
 74                     try {
 75                         condition.await();
 76                     } catch (InterruptedException e1) {
 77                         e1.printStackTrace();
 78                     }
 79                 }
 80                 u.flag = true; // 把共享变量flag变为true
 81                 System.out.println(u.name + "----" + u.age);
 82                 condition.signal();
 83 
 84             } catch (Exception e) {
 85 
 86             } finally {
 87                 u.lock.unlock();
 88             }
 89 
 90         }
 91     }
 92 }
 93 
 94 // 模拟生产者和消费者,线程间的通讯
 95 public class ThreadDemo4 {
 96     public static void main(String[] args) {
 97         User u = new User();
 98         Condition condition = u.lock.newCondition();
 99         Input i = new Input(u, condition);
100         Out o = new Out(u, condition);
101         i.start();
102         o.start();
103     }
104 
105 }

以上这几种就是配合使用的线程通讯问题的解决方式。

 


以上是关于线程间的通讯的主要内容,如果未能解决你的问题,请参考以下文章

利用线程间的通讯实现的水池加水放水

同一进程间的线程共享资源

利用QSystemSemaphore和QSharedMemory实现进程间通讯

Java线程之间通讯

线程之间的通讯问题

进程间通讯