java面试-Java并发编程(六)——线程间的通信
多条线程之间有时需要数据交互,下面介绍五种线程间数据交互的方式,他们的使用场景各有不同。
1. volatile、synchronized关键字
PS:关于volatile的详细介绍请移步至:Java并发编程的艺术(三)——volatile
1.1 如何实现通信?
这两种方式都采用了同步机制实现多条线程间的数据通信。与其说是“通信”,倒不如说是“共享变量”来的恰当。当一个共享变量被volatile修饰 或 被同步块包裹后,他们的读写操作都会直接操作共享内存,从而各个线程都能看到共享变量最新的值,也就是实现了内存的可见性。
1.2 特点
- 这种方式本质上是“共享数据”,而非“传递数据”;只是从结果来看,数据好像是从写线程传递到了读线程;
- 这种通信方式无法指定特定的接收线程。当数据被修改后究竟哪条线程最先访问到,这由操作系统随机决定。
- 总的来说,这种方式并不是真正意义上的“通信”,而是“共享”。
1.3 使用场景
这种方式能“传递”变量。当需要传递一些公用的变量时就可以使用这种方式。如:传递boolean flag,用于表示状态、传递一个存储所有任务的队列等。
1.4 例子
用这种方式实现线程的开关控制。
// 用于控制线程当前的执行状态
private volatile boolean running = false;// 开启一条线程
Thread thread = new Thread(new Runnable(){void run(){// 开关while(!running){Thread.sleep(1000);}// 执行线程任务doSometing();}
}).start();// 开始执行
public void start(){running = true;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
2. 等待/通知机制
2.1 如何实现?
等待/通知机制的实现由Java完成,我们只需调用Object类的几个方法即可。
- wait():将当前线程的状态改为“等待态”,加入等待队列,释放锁;直到当前线程发生中断或调用了notify方法,这条线程才会被从等待队列转移到同步队列,此时可以开始竞争锁。
- wait(long):和wait()功能一样,只不过多了个超时动作。一旦超时,就会继续执行wait之后的代码,它不会抛超时异常!
- notify():将等待队列中的一条线程转移到同步队列中去。
- notifyAll():将等待队列中的所有线程都转移到同步队列中去。
2.2 注意点
- 以上方法都必须放在同步块中;
- 并且以上方法都只能由所处同步块的锁对象调用;
- 锁对象A.notify()/notifyAll()只能唤醒由锁对象A wait的线程;
- 调用notify/notifyAll函数后仅仅是将线程从等待队列转移到阻塞队列,只有当该线程竞争到锁后,才能从wait方法中返回,继续执行接下来的代码;
2.3 QA
- 为什么wait必须放在同步块中调用?
因为等待/通知机制需要和共享状态变量配合使用,一般是先检查状态,若状态为true则执行wait,即包含“先检查后执行”,因此需要把这一过程加锁,确保其原子执行。
举个例子:
// 共享的状态变量
boolean flag = false;// 线程1
Thread t1 = new Thread(new Runnable(){public void run(){while(!flag){wait();}}
}).start();// 线程2
Thread t2 = new Thread(new Runnable(){public void run(){flag = true;notifyAll();}
}).start();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
上述例子thread1未加同步。当thread1执行到while那行后,判断其状态为true,此时若发生上下文切换,线程2开始执行,并一口气执行完了;此时flag已经是true,然而thread1继续执行,遇到wait后便进入等待态;但此时已经没有线程能唤醒它了,因此就一直等待下去。
为什么notify需要加锁?且必须和wait使用同一把锁?
首先,加锁是为了保证共享变量的内存可见性,让它发生修改后能直接写入共享内存,好让wait所处的线程立即看见。
其次,和wait使用同一把锁是为了确保wait、notify之间的互斥,即:同一时刻,只能有其中一条线程执行。为什么必须使用同步块的锁对象调用wait函数?
首先,由于wait会释放锁,因此通过锁对象调用wait就是告诉wait释放哪个锁。
其次,告诉线程,你是在哪个锁对象上等待的,只有当该锁对象调用notify时你才能被唤醒。为什么必须使用同步块的锁对象调用notify函数?
告诉notify,只唤醒在该锁对象上等待的线程。
2.4 代码实现
等待/通知机制用于实现生产者和消费者模式。
- 生产者
synchronized(锁A){flag = true;// 或者:list.add(xx);锁A.notify();
}
- 1
- 2
- 3
- 4
- 消费者
synchronized(锁A){// 不满足条件while(!flag){ // 或者:list.isEmpty()锁A.wait();}// doSometing……
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
2.5 超时等待模式
在之前的生产者-消费者模式中,如果生产者没有发出通知,那么消费者将永远等待下去。为了避免这种情况,我们可以给消费者增加超时等待功能。该功能依托于wait(long)方法,只需在wait前的检查条件中增加超时标识位,实现如下:
public void get(long mills){synchronized( list ){// 不加超时功能if ( mills <= 0 ) {while( list.isEmpty() ){list.wait();}}// 添加超时功能else {boolean isTimeout = false;while(list.isEmpty() && isTimeout){list.wait(mills);isTimeout = true;}// doSometing……}}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
3. 管道流
3.1 作用
管道流用于在两个线程之间进行字节流或字符流的传递。
3.2 特点
- 管道流的实现依靠PipedOutputStream、PipedInputStream、PipedWriter、PipedReader。分别对应字节流和字符流。
- 他们与IO流的区别是:IO流是在硬盘、内存、Socket之间流动,而管道流仅在内存中的两条线程间流动。
3.3 实现
步骤如下:
1. 在一条线程中分别创建输入流和输出流;
2. 将输入流和输出流连接起来;
3. 将输入流和输出流分别传递给两条线程;
4. 调用read和write方法就可以实现线程间通信。
// 创建输入流与输出流对象
PipedWriter out = new PipedWriter();
PipedReader in = new PipedReader();// 连接输入输出流
out.connect(in);// 创建写线程
class WriteThread extends Thread{private PipedWriter out;public WriteThread(PipedWriter out){this.out = out;}public void run(){out.write("hello concurrent world!");}
}// 创建读线程
class ReaderThread extends Thread{private PipedReader in;public ReaderThread(PipedReader in){this.in = in;}public void run(){in.read();}
}//
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
4. join
4.1 作用
- join能将并发执行的多条线程串行执行;
- join函数属于Thread类,通过一个thread对象调用。当在线程B中执行threadA.join()时,线程B将会被阻塞(底层调用wait方法),等到threadA线程运行结束后才会返回join方法。
- 被等待的那条线程可能会执行很长时间,因此join函数会抛出InterruptedException。当调用threadA.interrupt()后,join函数就会抛出该异常。
4.2 实现
public static void main(String[] args){// 开启一条线程Thread t = new Thread(new Runnable(){public void run(){// doSometing}}).start();// 调用join,等待t线程执行完毕try{t.join();}catch(InterruptedException e){// 中断处理……}}
java面试-Java并发编程(六)——线程间的通信相关推荐
- 19、Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition
Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...
- java判断线程是否wait_Java并发编程之线程间通讯(上)wait/notify机制
线程间通信 如果一个线程从头到尾执行完也不和别的线程打交道的话,那就不会有各种安全性问题了.但是协作越来越成为社会发展的大势,一个大任务拆成若干个小任务之后,各个小任务之间可能也需要相互协作最终才能执 ...
- 线程join_Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)
点击上方"Coder编程",选择"置顶公众号" 技术文章第一时间送达! 并发编程.png 每天进步一点,不做curd工程师与Api调用工程师 欢迎访问 个人博客 ...
- 【Java面试】并发编程实战(线程控制操作详解)
个人简介
- java task和thread_【Java学习笔记-并发编程】线程与任务
前言 最近在看一些Java15的并发.线程调度以及一些实现方案的东西,虽然很多东西还是 1.5 的,但还是很有收获. 一.线程与任务 Java中,要用线程来执行任务,线程可以说是任务的容器.没有线程的 ...
- 【Java 并发编程】线程简介 ( 进程与线程 | 并发概念 | 线程间通信 | Java 并发 3 特性 )
文章目录 一.进程与线程 二.并发 三.线程间通信 四.Java 并发 3 特性 一.进程与线程 最开始是没有线程这个概念的 , 一个应用程序就是一个进程 , 应用程序运行时 , 如果还要处理与用户交 ...
- Java并发基础(六) - 线程池
Java并发基础(六) - 线程池 1. 概述 这里讲一下Java并发编程的线程池的原理及其实现 2. 线程池的基本用法 2.1 线程池的处理流程图 该图来自<Java并发编程的艺术>: ...
- (转)Java并发编程:线程池的使用
背景:线程池在面试时候经常遇到,反复出现的问题就是理解不深入,不能做到游刃有余.所以这篇博客是要深入总结线程池的使用. ThreadPoolExecutor的继承关系 线程池的原理 1.线程池状态(4 ...
- 【Java 并发编程】线程池机制 ( ThreadPoolExecutor 线程池构造参数分析 | 核心线程数 | 最大线程数 | 非核心线程存活时间 | 任务阻塞队列 )
文章目录 前言 一.ThreadPoolExecutor 构造参数 二.newCachedThreadPool 参数分析 三.newFixedThreadPool 参数分析 四.newSingleTh ...
最新文章
- 跟我学Springboot开发后端管理系统4:数据库连接池Druid和HikariCP
- 程序员的爱情 第六章
- android如何查看分区信息,android如何查看分区信息
- 陶晶驰stm32_陶晶驰串口屏学习日记(1)
- ajax nginx 转发 sessionid_「查缺补漏」巩固你的Nginx知识体系
- 试用到期_各大化妆品品牌试用装广告
- 关于C++中的 多态 问题
- Tarjan算法——强连通分量
- 视频人像磨皮插件:Beauty Box 4.2
- 51单片机汇编程序,温度报警项目
- wdr7660虚拟服务器设置,TP-LINK WDR7660用手机怎么设置?
- C语言的进制转换以及算法实现
- java 策略模式会员_设计模式——策略模式:会员价格体系的简单实现
- android 支付宝私钥加密,支付宝支付密钥RSA1升级到RSA2
- 官宣,北京杜绝现场复试!清华等全国多地高校确定将网络远程复试
- Arch Linux 安装后无法联网的问题
- linux 实验心得体会
- macOS Chrome无法访问自签名https页面问题的解决办法
- 李飞飞CS231n2017课程双语字幕版上线 !(附课程链接)
- UiPath Robotic Enterprise Framework 学习笔记
热门文章
- ie6 ie7下使用clear不能将浮动的元素换行问题
- Thinking In Design Pattern——Query Object模式
- SWT多线程注意事项
- Linux下tomcat的配置
- wex5链接mysql_wex5数据库连接自己的数据库在哪里配置的
- python实现守护进程_守护进程原理及Python实现
- Golang——深浅拷贝
- c盘java文件误删_C盘爆满怎么办,教你有选择性删除文件,恢复空间
- android系统源码的环境下用make来编译,Android源码编译系统入门
- php switch if,php switch 与 if else 区别