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

  • 可以利用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. Java集合及Concurrent并发包总结

    1.集合包 集合包最常用的有Collection和Map两个接口的实现类,Colleciton用于存放多个单对象,Map用于存放Key-Value形式的键值对. Collection中最常用的又分为两 ...

  2. SQL语句 运算符

    6.2 运算符   6.2.1 算术运算符 加 / 减 / 乘 / 除 6.2.2 连接运算符 是用来连接字符串的.跟java中的 + 是一致的. select 'abc' || ' bcd ' as ...

  3. 【NOIP2016提高A组模拟9.17】序列

    题目 分析 首先用\(a_i\)表示达到目标的步数\(B_i-A_i(mod 4)\) 根据粉刷栅栏,先不管mod 4的情况,答案就是\(\sum\max(a_i-a_{i+1},0)\) 那我们刚才 ...

  4. 【NOIP2016提高A组模拟8.19】(雅礼联考day2)树上路径

    题目 给出一棵树,求出最小的k,使得,且在树中存在路径p,使得k>=S且k<=E.(k为路径p上的边的权值和). 分析 点分治,设当前为x的,求在以x为根的子树中,经过x的路径(包括起点或 ...

  5. shell练习--关于二维数组的实现问题

    关于二维数组的实现问题:通过查询网友们的记录,可知有两个方法 方法1:通过通过2次读取赋值,来获取二维数组的方法. a=('1 2 3' '4 5 6' '7 8 9') for i in " ...

  6. ASP教程:gb2312和utf-8乱码问题解决

    文章为转载:https://www.xp.cn/b.php/31663.html 尊重作者版权 今天做网站的时候,客户要一个博客,于是就利用了本博客所用的程序pjblog.经常做网站的人都知道,在同一 ...

  7. vscode匹配括号插件

    给大家推荐一个vscode匹配括号的插件: Bracket Pair Colorizer.超级好用哦

  8. datatable 和实体互转

    public static class ModelConvertHelper<T> where T : class,new() { public static List<T> ...

  9. MFC多文档获取窗口句柄

    GET App   AfxGetInstanceHandle()    AfxGetApp()   GET Frame->View->Document     SDI    AfxGetM ...

  10. new 做了什么

    var a=function(){ this.che1 = function () { console.log(1) } this.che2 = function () { console.log(2 ...