此题考查的是线程间的通信方式。

  • 可以利用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的更多相关文章

  1. 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. 写2个线程,一个打印1-52,一个打印A-Z,打印顺序是12A34B。。。(采用同步代码块和同步方法两种同步方法)

    1.同步方法 package Synchronized; /************************************同步方法****************************** ...

  3. java 通过控制台输入的数字打印菱形字母

    package com.rui.test; import java.util.Scanner; /** * @author sunshine * @version 1.0 * @date:2015年1 ...

  4. Python输入一个数字打印等腰三角形

    要求 用户输入一个数字,按照数字打印出等腰三角形 思路 1,用户输入的数字为n代表一共有多少行 2,使用一个循环带两个for循环,第一层循环是循环行数,第二层两个平行for循环一个打印空格一个打印*号 ...

  5. C++版 - 剑指offer 面试题23:从上往下打印二叉树(二叉树的层次遍历BFS) 题解

    剑指offer  面试题23:从上往下打印二叉树 参与人数:4853  时间限制:1秒  空间限制:32768K 提交网址: http://www.nowcoder.com/practice/7fe2 ...

  6. 数字序列中某一位数字(《剑指offer》面试题44)

    由于这道题目在牛客上没有,所以在此记录一下. 一.题目大意: 数字以0123456789101112131415…的格式序列化到一个字符序列中.在这个序列中,第5位(从0开始计数,即从第0位开始)是5 ...

  7. 每隔10秒钟打印一个“Helloworld”

    /** * 每隔10秒钟打印一个“Helloworld” */ public class Test03 { public static void main(String[] args) throws ...

  8. 有三个线程,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; /** * 测试三个线程协 ...

  9. 定义一个类:实现功能可以返回随机的10个数字,随机的10个字母, 随机的10个字母和数字的组合;字母和数字的范围可以指定,类似(1~100)(A~z)

    #习题2:定义一个类:实现功能可以返回随机的10个数字,随机的10个字母, #随机的10个字母和数字的组合:字母和数字的范围可以指定 class RandomString(): #随机数选择的范围作为 ...

随机推荐

  1. UI控件Telerik UI for Silverlight发布R2 2019|附下载

    Telerik UI for Silverlight包含了超过100个能用于纯Silverlight应用程序中或能作为现有ASP.NET应用程序的一部分的UI控件.通过与我们的WPF控件共享一个相同的 ...

  2. vs2017 mvc 启动时经常出现调用的目标发生异常

    1.vs  2017 调试web 程序时老是出现调用的目标发生异常 本人眼拙,基本上看了网站说的一些方法,设置环境变量是无效的,只有一个办法,卸载重装. 1.0 卸载过程 打开计算机-卸载或更改软件- ...

  3. 微信小程序中的自定义组件(components)

     其实小程序开发很像vue和react的结合,数据绑定和setData  重新渲染页面的数据,最近发现连写组件都是很像,也是醉了,自我认为哈, 因为小程序可以将页面内的功能模块抽象成自定义组件,以便在 ...

  4. 集合综合练习<一>

    1208421001,关羽,数学,93 1208421001,关羽,英语,88 1208421002,张飞,语文,82 1208421002,张飞,数学,83 1208421002,张飞,英语,89 ...

  5. pyqt5-动画组QAnimationGroup

    from PyQt5.QtWidgets import QApplication, QWidget,QPushButton,QLabel import sys from PyQt5.QtCore im ...

  6. raster导入postgres Windows命令

    cmd命令行 raster2pgsql -s 4326 -I -C -M C:\Users\tt\Downloads\tmean_19_tif\*.tif -F -t 256x256 tmean_19 ...

  7. kvm:双网卡做bond+桥接

    一,KVM基础 kvm是一种技术,云计算是一种模式,虚拟化是利用相应的技术方法在一台物理机器上将其按照不同的需求划分成多个相同或者不同的虚拟操作系统,并且各个虚拟系统可以同时运行,互不干扰,其中任何一 ...

  8. LCA模板 ( 最近公共祖先 )

    LCA 有几种经典的求取方法.这里只给出模板,至于原理我完全不懂. 1.RMQ转LCA.复杂度O(n+nlog2n+m) 大致就是 DFS求出欧拉序 => 对欧拉序做ST表 => LCA( ...

  9. 微信浏览器video播放视频踩坑

    video属性介绍 iOS的属性 playsinline On iPhone, video playsinline elements will now be allowed to play inlin ...

  10. 【技术分享:python 应用之二】解锁用 VSCode 写 python 的正确姿势

    之前一直用 notepad++ 作为编辑器,偶然发现了 VScode 便被它的颜值吸引.用过之后发现它启动快速,插件丰富,下载安装后几乎不用怎么配置就可以直接使用,而且还支持 markdown.当然, ...