先看一个问题:

有两个线程,子线程先执行10次,然后主线程执行5次,然后再切换到子线程执行10,再主线程执行5次……如此往返执行50次。

看完这个问题,很明显要用到线程间的通信了, 先分析一下思路:首先肯定要有两个线程,然后每个线程中肯定有个50次的循环,因为每个线程都要往返执行任务50次,主线程的任务是执行5次,子线程的任务是执行10次。线程间通信技术主要用到 wait() 方法和 notify() 方法。wait() 方法会导致当前线程等待,并释放所持有的锁,notify() 方法表示唤醒在此对象监视器上等待的单个线程。下面来一步步完成这道线程间通信问题。

首先不考虑主线程和子线程之间的通信,先把各个线程所要执行的任务写好:

public class TraditionalThreadCommunication {

	public static void main(String[] args) {
//开启一个子线程
new Thread(new Runnable() { @Override
public void run() {
for(int i = 1; i <= 50; i ++) { synchronized (TraditionalThreadCommunication.class) {
//子线程任务:执行10次
for(int j = 1;j <= 10; j ++) {
System.out.println("sub thread sequence of " + j + ", loop of " + i);
}
}
} }
}).start(); //main方法即主线程
for(int i = 1; i <= 50; i ++) { synchronized (TraditionalThreadCommunication.class) {
//主线程任务:执行5次
for(int j = 1;j <= 5; j ++) {
System.out.println("main thread sequence of " + j + ", loop of " + i);
}
}
}
}
}

如上,两个线程各有50次大循环,执行50次任务,子线程的任务是执行10次,主线程的任务是执行5次。为了保证两个线程间的同步问题,所以用了 synchronized 同步代码块,并使用了相同的锁:类的字节码对象。这样可以保证线程安全。但是这种设计不太好,就像我在上一节的死锁中写的一样,我们可以把线程任务放到一个类中,这种设计的模式更加结构化,而且把不同的线程任务放到同一个类中会很容易解决同步问题,因为在一个类中很容易使用同一把锁。所以把上面的程序修改一下:

public class TraditionalThreadCommunication {

	public static void main(String[] args) {
Business bussiness = new Business(); //new一个线程任务处理类
//开启一个子线程
new Thread(new Runnable() { @Override
public void run() {
for(int i = 1; i <= 50; i ++) {
bussiness.sub(i);
} }
}).start(); //main方法即主线程
for(int i = 1; i <= 50; i ++) {
bussiness.main(i);
}
} }
//要用到的共同数据(包括同步锁)或共同的若干个方法应该归在同一个类身上,这种设计正好体现了高类聚和程序的健壮性。
class Business { public synchronized void sub(int i) { for(int j = 1;j <= 10; j ++) {
System.out.println("sub thread sequence of " + j + ", loop of " + i);
}
} public synchronized void main(int i) { for(int j = 1;j <= 5; j ++) {
System.out.println("main thread sequence of " + j + ", loop of " + i);
}
}

经过这样修改后,程序结构更加清晰了,也更加健壮了,只要在两个线程任务方法上加上 synchronized 关键字即可,用的都是 this 这把锁。但是现在两个线程之间还没有通信,执行的结果是主线程循环执行任务50次,然后子线程再循环执行任务50次,原因很简单,因为有 synchronized 同步。

下面继续完善程序,让两个线程之间完成题目中所描述的那样通信:

public class TraditionalThreadCommunication {

	public static void main(String[] args) {
Business bussiness = new Business(); //new一个线程任务处理类
//开启一个子线程
new Thread(new Runnable() { @Override
public void run() {
for(int i = 1; i <= 50; i ++) {
bussiness.sub(i);
} }
}).start(); //main方法即主线程
for(int i = 1; i <= 50; i ++) {
bussiness.main(i);
}
} }
//要用到共同数据(包括同步锁)或共同的若干个方法应该归在同一个类身上,这种设计正好体现了高雷剧和程序的健壮性。
class Business {
private boolean bShouldSub = true; public synchronized void sub(int i) {
while(!bShouldSub) { //如果不轮到自己执行,就睡
try {
this.wait(); //调用wait()方法的对象必须和synchronized锁对象一致,这里synchronized在方法上,所以用this
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(int j = 1;j <= 10; j ++) {
System.out.println("sub thread sequence of " + j + ", loop of " + i);
}
bShouldSub = false; //改变标记
this.notify(); //唤醒正在等待的主线程
} public synchronized void main(int i) {
while(bShouldSub) { //如果不轮到自己执行,就睡
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(int j = 1;j <= 5; j ++) {
System.out.println("main thread sequence of " + j + ", loop of " + i);
}
bShouldSub = true; //改变标记
this.notify(); //唤醒正在等待的子线程
}
}

首先,先不说具体的程序实现,就从结构上来看,已经体会到了这种设计的好处了:主函数里不用修改任何东西,关于线程间同步和线程间通信的逻辑全都在 Business 类中,主函数中的不同线程只需要调用放在该类中对应的任务即可。体现了高类聚的好处。

  

再看一下具体的代码,首先定义一个 boolean 型变量来标识哪个线程该执行,当不是子线程执行的时候,它就睡,那么很自然主线程就执行了,执行完了,修改了 bShouldSub 并唤醒了子线程,子线程这时候再判断一下 while 不满足了,就不睡了,就执行子线程任务,同样地,刚刚主线程修改了 bShouldSub 后,第二次循环来执行主线程任务的时候,判断 while 满足就睡了,等待子线程来唤醒。这样逻辑就很清楚了,主线程和子线程你一下我一下轮流执行各自的任务,这种节奏共循环50次。

  

另外有个小小的说明:这里其实用 if 来判断也是可以的,但是为什么要用 while 呢?因为有时候线程会假醒(就好像人的梦游,明明正在睡,结果站起来了),如果用的是if的话,那么它假醒了后,就不会再返回去判断if了,那它就很自然的往下执行任务,好了,另一个线程正在执行呢,啪叽一下就与另一个线程之间相互影响了。但是如果是while的话就不一样了,就算线程假醒了,它还会判断一下 while 的,但是此时另一个线程在执行啊,bShouldSub 并没有被修改,所以还是进到 while 里了,又被睡了~所以很安全,不会影响另一个线程!官方 JDK 文档中也是这么干的。

线程间通信就总结到这吧~若有错误,欢迎指正,我们一起进步。

Java并发基础05. 传统线程同步通信技术的更多相关文章

  1. Java并发基础02. 传统线程技术中的定时器技术

    传统线程技术中有个定时器,定时器的类是Timer,我们使用定时器的目的就是给它安排任务,让它在指定的时间完成任务.所以先来看一下Timer类中的方法(主要看常用的TimerTask()方法): 前面两 ...

  2. Java并发基础03. 传统线程互斥技术—synchronized

    在多个线程同时操作相同资源的时候,就会遇到并发的问题,如银行转账啊.售票系统啊等.为了避免这些问题的出现,我们可以使用synchronized关键字来解决,下面针对synchronized常见的用法做 ...

  3. Java并发基础01. 传统线程技术中创建线程的两种方式

    传统的线程技术中有两种创建线程的方式:一是继承Thread类,并重写run()方法:二是实现Runnable接口,覆盖接口中的run()方法,并把Runnable接口的实现扔给Thread.这两种方式 ...

  4. Java多线程与并发库高级应用-传统线程同步通信技术

    面试题: 子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着又 主线程循环100次,如此循环50次,请写出程序 /** * 子线程循环10次,接着主线程循环100次,接着又回到 ...

  5. 【Java多线程与并发库】4.传统线程同步通信技术

    我们先通过一道面试题来了解传统的线程同步通信. 题目:子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着再回到主线程又循环100次,如此循环50次,请写出程序. 我没有看答案, ...

  6. java并发基础(五)--- 线程池的使用

    第8章介绍的是线程池的使用,直接进入正题. 一.线程饥饿死锁和饱和策略 1.线程饥饿死锁 在线程池中,如果任务依赖其他任务,那么可能产生死锁.举个极端的例子,在单线程的Executor中,如果一个任务 ...

  7. Java 并发基础

    Java 并发基础 标签 : Java基础 线程简述 线程是进程的执行部分,用来完成一定的任务; 线程拥有自己的堆栈,程序计数器和自己的局部变量,但不拥有系统资源, 他与其他线程共享父进程的共享资源及 ...

  8. java并发基础(二)

    <java并发编程实战>终于读完4-7章了,感触很深,但是有些东西还没有吃透,先把已经理解的整理一下.java并发基础(一)是对前3章的总结.这里总结一下第4.5章的东西. 一.java监 ...

  9. Java并发基础概念

    Java并发基础概念 线程和进程 线程和进程都能实现并发,在java编程领域,线程是实现并发的主要方式 每个进程都有独立的运行环境,内存空间.进程的通信需要通过,pipline或者socket 线程共 ...

随机推荐

  1. 谈谈集合.Queue

    之前说到,Java中集合的主要作用就是装盛其他数据和实现常见的数据结构.所以当我们要用到"栈"."队列"."链表"和"数组&quo ...

  2. Elasticsearch构建全文搜索系统

    目录 前言 一.安装 1.安装elasticsearch 2.启动集群cluster 3.安装管理界面elasticsearch-head 4.安装分词插件elasticsearch-analysis ...

  3. What is the difference between shades and shadows?

    Shade is the darkness of an object not in direct light, while shadows are the silhouette of an objec ...

  4. stm32的hall库新建模板编译错误: #error "Please select first the target STM32F1xx device used in your application (in stm32f1xx.h file)"的处理

    在stm32f1xx.h file文件中找到如下代码: /* Uncomment the line below according to the target STM32L device used i ...

  5. 利用java编写物品的品牌、尺寸、价格、库存(新手)

    //定义一个类 public class NV{ //公共静态的主方法 public static void main(String[] args){ //打印 “京东三九女神节” 标题 System ...

  6. Mybatis总结一之Mybatis项目的创建

    一.mybatis概念 Mybatis是对象和表之间映射关系的持久层框架. 二.Mybatis的导入与创建 第一步,创建web项目,引入mybatis依赖的jar包----mybatis-3.4.6. ...

  7. Vulnhub 靶场 Os-hackNos WP

    About Os-hackNos 描述 Difficulty : Easy to Intermediate Flag : 2 Flag first user And second root Learn ...

  8. 大数据软件安装之Flume(日志采集)

    一.安装地址 1) Flume官网地址 http://flume.apache.org/ 2)文档查看地址 http://flume.apache.org/FlumeUserGuide.html 3) ...

  9. Java高效编程:总结分享

    参考资料:慕课网:Java高效编程收费实战课程.博客园.CSDN.菜鸟教程以及其他文档. 篇幅受限,不太想针对每个点都写篇博客,有的地方可能写的不是很详细,一笔带过了.如果你觉得那个点在项目中用得上可 ...

  10. 升级Kubernetes 1.18前,你不得不知的9件事

    本文来自Rancher Labs 昨天Kubernetes最新版本v1.18已经发布,其包含了38项功能增强,其中15项为稳定版功能.11项beta版功能以及12项alpha版功能.在本文中,我们将探 ...