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

  • 可以利用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. Vue的css动画原理

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. iOS给UIView添加点击事件

    我要给一个UIView对象topView添加点击事件,方法如下: 步骤1:创建手势响应函数 (void)event:(UITapGestureRecognizer *)gesture { //处理事件 ...

  3. 【NOIP2016提高A组五校联考1】挖金矿

    题目 分析 我们二分答案 设\(sum_{i,j}\)表示的i列前个数的和, 假设当前出的二分答案为x,第i列挖了\(h_j\)层,则 \[\dfrac{\sum_{i=1}^{n}sum_{i,h_ ...

  4. 【NOIP2016提高A组模拟8.17】(雅礼联考day1)Value

    题目 分析 易证,最优的答案一定是按\(w_i\)从小到大放. 我们考虑dp, 先将w从小到大排个序,再设\(f_{i,j}\)表示当前做到第i个物品,已选择了j个物品的最大值.转移就是\[f_{i, ...

  5. C# 、Java数组申明、初始化区别

    一.数组申明   int[] a1 int a2[] C# 支持 不支持 Java 支持 支持 二.数组申明且初始化    int[] a1 = new int[] { 2, 31 } int a1[ ...

  6. Vue-Router的简单使用

    Vue-Router的最简单使用 1.先注册路由 2.将路由注册到VM组件中 3.定义组件 4.页面定义跳转路径 <!DOCTYPE html> <html> <head ...

  7. 牛客网 TaoTao要吃鸡 ( 0/1背包变形 )

    题意 : 题目链接 分析 :  如果没有 BUG (即 h == 0 的时候)就是一个普通的 0 / 1 背包 需要讨论一下 h != 0 的情况 此时有就相当于有物品是有特权的 而且背包装有特权的物 ...

  8. 我的Android案例签到日历

    2015年的Android案例之旅 案例八:签到日历 知识点: GridView的使用SQLite的使用 涉及文件: res->layout->activity_main.xml 主布局文 ...

  9. Linux基本命令使用(三)

    1.压缩解压命令:gzip,   .gz格式的 gzip 文件名     就压缩了. Linux压缩的放到Windows下可以解压,但是Windows下压缩到Linux解压就不一定可以. (1)只能压 ...

  10. 值不能为空。参数名viewinfo(microsoft.sqlserver.management.sqlstudio.explorer)

    打开MSSQL 2008 R2的时候,展开数据库都显示以下的错误提示: 值不能为空.参数名viewinfo(microsoft.sqlserver.management.sqlstudio.explo ...