线程间的通讯
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()、notify、notifyAll()方法
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() 才能够解锁它,所以他们两个互相解锁唤醒,就实现了通讯的功能。
并且不能单独使用必须在线程同步中使用!!!否则就会报错
wait与sleep区别?
对于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 }
以上这几种就是配合使用的线程通讯问题的解决方式。
以上是关于线程间的通讯的主要内容,如果未能解决你的问题,请参考以下文章