面试题:线程A打印1-10数字,打印到第5个数字时,通知线程B
此题考查的是线程间的通信方式。
- 可以利用park/unpark实现
- 可以利用volatile关键字实现
- 可以利用synchronized结合wait notify实现
- 可以利用JUC中的CountDownLatch实现
- 可以利用Condition中的await signal 实现
代码示例
利用Park/Unpak实现线程通信
private void notifyThreadWithParkUnpark(){
Thread thb = new Thread("线程B"){
@Override
public void run() {
LockSupport.park();
System.out.println(Thread.currentThread().getName()+"启动了");
}
};
Thread tha =new Thread("线程A"){
@Override
public void run() {
for(int i=1;i<11;i++){
System.out.println(Thread.currentThread().getName()+i);
if(i==5){
LockSupport.unpark(thb);
}
}
}
};
thb.start();
tha.start();
}
park与unpark可以看做一个令牌,park就是等待令牌,unpark就是颁发一个令牌,另外需要注意的是park与unpark的调用次数不用一一对应,而且假如在同步代码块中调用park方法,线程会进入阻塞状态,但是不会释放已经占用的锁。
本例使用park使线程B进入阻塞等待状态,在线程A调用unpark并传入线程B的名称使线程B可以继续运行。
使用Volatile关键字实现线程通信
private static volatile boolean flag = false;
private void notifyThreadWithVolatile(){
Thread thc= new Thread("线程C"){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if(i==5){
flag=true;
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+i);
}
}
};
Thread thd= new Thread("线程D"){
@Override
public void run() {
while (true){
// 防止伪唤醒 所以使用了while
while(flag){
System.out.println(Thread.currentThread().getName()+"收到通知");
break;
}
}
}
};
thd.start();
try {
Thread.sleep(1000L);
} catch (Exception e) {
e.printStackTrace();
}
thc.start();
}
volatile表示的禁用CPU缓存,用volatile修饰的变量,会强制从主内存中读取变量的值。java内存模型中关于volatile也是有说明的,volatile只能保证可见性,但不能保证原子性。
本例通过在volatile来修饰一个标志位,线程C修改了该标志位,然后线程D就可以“看到”标志位的修改,从而实现互相通信。
使用Synchronized 集合wait notify实现线程间通信
private static final Object lock = new Object();
private void notifyThreadWithSynchronized(){
Thread the = new Thread("线程E"){
@Override
public void run() {
synchronized (lock){
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+i);
if(i==5){
lock.notify();
}
}
}
}
};
Thread thf = new Thread("线程F"){
@Override
public void run() {
while(true){
synchronized (lock){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"启动了");
}
}
}
};
thf.start();
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
the.start();
}
synchronized修饰同步代码块,而wait notify notify必须是在synchronized修饰代码块中使用,否则会抛出监视器异常。
本实例定义一个对象锁,而线程F首先获取到互斥锁,在执行wait()方法时,释放已经持有的互斥锁,进入等待队列。而线程E执行获取到互斥锁开始执行,当1==5时,调用notify方法,就会通知lock的等待队列,然后线程E会继续执行,由于线程F此时还是获取不到互斥锁(因为被线程E占用),所以会在线程E执行完毕后,才能获取到执行权。
利用CountDonwLatch实现线程间通信
// 倒计时器
private CountDownLatch cdl = new CountDownLatch(1);
private void notifyThreadWithCountDownLatch(){
Thread thg = new Thread("线程G"){
@Override
public void run() {
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"启动了");
}
};
thg.start();
Thread thh = new Thread("线程H"){
@Override
public void run() {
for (int i = 1; i < 11; i++) {
System.out.println(Thread.currentThread().getName()+i);
if(i==5){
cdl.countDown();
}
}
}
};
thh.start();
}
本示例中使用了CountDownLatch倒计时器,利用了倒计时器的阻塞特性来实现等待。具体就是声明一个计数器为1的倒计时器,线程G调用await()方法进入等待,直到计数器为0的时候才能够进入执行,而线程H在i==5会将计数器减一,使其为0,此时线程G就会继续执行了。
利用Condition中的await和signal来实现
// ReentrantLock+ condition
private Lock rtl=new ReentrantLock();
private Condition condition = rtl.newCondition();
private void notifyThreadWithCondition(){
Thread thi = new Thread("线程I"){
@Override
public void run() {
while (true){
rtl.lock();
try {
condition.await();
System.out.println(Thread.currentThread().getName()+"启动了");
break;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
rtl.unlock();
}
}
}
};
Thread thj = new Thread("线程J"){
@Override
public void run() {
rtl.lock();
try {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+i);
if(i==5){
condition.signal();
}
}
} finally {
rtl.unlock();
}
}
};
thi.start();
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
thj.start();
}
本示例是结合ReentrantLock和Condition来进行控制线程间的执行顺序,Condition的await()和signal(),他们的语义和wait notify是一样的。区别是在synchronized代码块里调用wait notify。通过示例可以看到这中方法实现会不断的加锁与解锁,所以看起来稍微复杂些。
总结
通过以上代码看到通过volatile的方式是最简洁方便,用park与unpark方式是比较灵活,不用加锁或解锁,剩下的synchronized与Conditon都是用了锁,而CountDownLatch则是利用了计数器。
面试题:线程A打印1-10数字,打印到第5个数字时,通知线程B的更多相关文章
- 4.产生10个1-100的随机数,并放到一个数组中 (1)把数组中大于等于10的数字放到一个list集合中,并打印到控制台。 (2)把数组中的数字放到当前文件夹的numArr.txt文件中
package cn.it.text; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayLis ...
- 写2个线程,一个打印1-52,一个打印A-Z,打印顺序是12A34B。。。(采用同步代码块和同步方法两种同步方法)
1.同步方法 package Synchronized; /************************************同步方法****************************** ...
- java 通过控制台输入的数字打印菱形字母
package com.rui.test; import java.util.Scanner; /** * @author sunshine * @version 1.0 * @date:2015年1 ...
- Python输入一个数字打印等腰三角形
要求 用户输入一个数字,按照数字打印出等腰三角形 思路 1,用户输入的数字为n代表一共有多少行 2,使用一个循环带两个for循环,第一层循环是循环行数,第二层两个平行for循环一个打印空格一个打印*号 ...
- C++版 - 剑指offer 面试题23:从上往下打印二叉树(二叉树的层次遍历BFS) 题解
剑指offer 面试题23:从上往下打印二叉树 参与人数:4853 时间限制:1秒 空间限制:32768K 提交网址: http://www.nowcoder.com/practice/7fe2 ...
- 数字序列中某一位数字(《剑指offer》面试题44)
由于这道题目在牛客上没有,所以在此记录一下. 一.题目大意: 数字以0123456789101112131415…的格式序列化到一个字符序列中.在这个序列中,第5位(从0开始计数,即从第0位开始)是5 ...
- 每隔10秒钟打印一个“Helloworld”
/** * 每隔10秒钟打印一个“Helloworld” */ public class Test03 { public static void main(String[] args) throws ...
- 有三个线程,a、b、c,a打印“T1”,b打印“T2”,c打印“T3”,a执行完后,b执行;b执行完后,c执行。如此循环100遍
有三个线程,a.b.c,a打印“T1”,b打印“T2”,c打印“T3”,a执行完后,b执行:b执行完后,c执行.如此循环100遍. package com.company; /** * 测试三个线程协 ...
- 定义一个类:实现功能可以返回随机的10个数字,随机的10个字母, 随机的10个字母和数字的组合;字母和数字的范围可以指定,类似(1~100)(A~z)
#习题2:定义一个类:实现功能可以返回随机的10个数字,随机的10个字母, #随机的10个字母和数字的组合:字母和数字的范围可以指定 class RandomString(): #随机数选择的范围作为 ...
随机推荐
- DevExpress WPF v19.1新版亮点:Scheduler等控件新功能
行业领先的.NET界面控件DevExpress 日前正式发布v19.1版本,本站将以连载的形式介绍各版本新增内容.在本系列文章中将为大家介绍DevExpress WPF v19.1中新增的一些控件及部 ...
- DevExpress WPF v19.1:Data Grid/Tree List等控件功能增强
行业领先的.NET界面控件DevExpress 日前正式发布v19.1版本,本站将以连载的形式介绍各版本新增内容.在本系列文章中将为大家介绍DevExpress WPF v19.1中新增的一些控件及部 ...
- 常用cmd命令总结
1.常用操作 cls #清屏set #查看环境变量cd #切换工作目录 (换盘:直接输入 C: 或 D:)cd.. #返回上级目录exit #关闭cmd窗口 2.有关Python pip instal ...
- 【leetcode database】Human Traffic of Stadium
X city built a new stadium, each day many people visit it and the stats are saved as these columns: ...
- CentOS8 中文输入法
CentOS8发布了,安装了下试试,结果发现中文输入法调不出来. 系统安装完成后,在系统[设置]的[Region&Language]里的[输入源]里可以添加汉语输入源,但是不能打中文字. 下面 ...
- [人物存档]【AI少女】【捏脸数据】两个人物
点击下载(城通网盘):8bcd58f40ad162d9c1fd6f641edfa9ec8b13cdf8.png 点击下载(城通网盘):AISChaF_20191110015122738.png
- zabbix 磁盘自动发现脚本
##需要在zabbix界面配置宏变量===>正则来匹配磁盘 disk_discovery.sh ———————————————————————————————————————————————— ...
- js能否上传文件夹
文件夹上传:从前端到后端 文件上传是 Web 开发肯定会碰到的问题,而文件夹上传则更加难缠.网上关于文件夹上传的资料多集中在前端,缺少对于后端的关注,然后讲某个后端框架文件上传的文章又不会涉及文件夹. ...
- Codeforces 919D Substring ( 拓扑排序 && DAG上的DP )
题意 : 给出含有 N 个点 M 条边的图(可能不连通或者包含环),每个点都标有一个小写字母编号,然后问你有没有一条路径使得路径上重复字母个数最多的次数是多少次,例如图上有条路径的顶点标号顺序是 a ...
- 算法设计与分析 1.2 不一样的fibonacci数列 (矩阵快速幂思想)
题目描述 Winder 最近在学习 fibonacci 数列的相关知识.我们都知道 fibonacci 数列的递推公式是F(n) = F(n - 1) + F(n - 2)(n >= 2 且 n ...