上一篇文章我们讲解了线程间的互斥技术,使用关键字synchronize来实现线程间的互斥技术。根据不同的业务情况,我们可以选择某一种互斥的方法来实现线程间的互斥调用。例如:自定义对象实现互斥(synchronize("自定义对象"){}),同一个类实例对象(synchronize(this){}),类的字节码对象(synchronize(字节码对象){})。这三种方法均可实现线程间的互斥,我们实际运用中灵活使用。

下面进入今天的正题:线程--线程间的同步通信技术;我们还是以传智播客中的代码为例来讲解,子线程运行10次,主线程运行10次,如此交替运行50次。

首先看不适用同步技术时的问题代码:

public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for(int i=1;i<=50;i++){
for(int j=1;j<=10;j++){
System.out.println("子线程:"+Thread.currentThread().getName()+"运行第"+i+"次,重复第"+j+"次");
}
}
}
}).start(); for(int i=1;i<=50;i++){
for(int j=1;j<=10;j++){
System.out.println("主线程:"+Thread.currentThread().getName()+"运行第"+i+"次,重复第"+j+"次");
}
}
}

上面代码的运行结果可知,子线程与主线程间是杂乱无章的运行,显然不能满足我的要求。那我们来稍作调整。代码如下:

public class ThreadSynchronizedTechnology {

	public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for(int i=1;i<=50;i++){
                       //使用类的字节码作为互斥对象
synchronized(ThreadSynchronizedTechnology.class){
for(int j=1;j<=10;j++){
System.out.println("子线程:"+Thread.currentThread().getName()+"运行第"+i+"次,重复第"+j+"次");
}
}
}
}
}).start(); for(int i=1;i<=50;i++){
synchronized(ThreadSynchronizedTechnology.class){
for(int j=1;j<=10;j++){
System.out.println("主线程:"+Thread.currentThread().getName()+"运行第"+i+"次,重复第"+j+"次");
}
}
}
} }

上面的代码我们使用类的字节码作为互斥对象,显然程序不再是杂乱无章的运行,子线程与主线程都能完整的运行完,但是没有实现我们要求的交替运行,不要急我接着调整,我非常喜欢张孝祥老师循序渐进的讲课方式(我不是托,是发自内心的),下面我们接着调整:

public class ThreadSynchronizedTechnology {
public static void main(String[] args) {
final Songzl song = new Songzl();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=1;i<=50;i++){
song.sub(i);
}
}
}).start(); for(int i=1;i<=50;i++){
song.main(i);
}
}
}
class Songzl{
//子线程运行的方法
public void sub(int i){
synchronized(Songzl.class){
for(int j=1;j<=10;j++){
System.out.println("子线程:"+Thread.currentThread().getName()+"运行第"+i+"次,重复第"+j+"次");
}
}
}
//主线程运行的方法
public void main(int i){
synchronized(Songzl.class){
for(int j=1;j<=10;j++){
System.out.println("主线程:"+Thread.currentThread().getName()+"运行第"+i+"次,重复第"+j+"次");
}
}
}
}

打印结果依然不是交替运行,我调成这样是为了体现编程的面向对象思想,将相关联的方法封装到同一个类中,方便代码运维。我们接着调整代码:

public class ThreadSynchronizedTechnology {
public static void main(String[] args) {
final Songzl song = new Songzl();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=1;i<=50;i++){
song.sub(i);
}
}
}).start(); for(int i=1;i<=50;i++){
song.main(i);
}
}
}
class Songzl{
//实现线程同步通信,互斥的方法共享次变量
private boolean jiaoti = true;
//子线程运行的方法:同一个类中方法互斥,类似与synchronized(this){}
public synchronized void sub(int i) {
if(!jiaoti){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for(int j=1;j<=10;j++){
System.out.println("子线程:"+Thread.currentThread().getName()+"运行第"+i+"次,重复第"+j+"次");
}
jiaoti = false;
this.notify();
}
//主线程运行的方法:同一个类中方法互斥,类似与synchronized(this){}
public synchronized void main(int i){
if(jiaoti){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for(int j=1;j<=10;j++){
System.out.println("主线程:"+Thread.currentThread().getName()+"运行第"+i+"次,重复第"+j+"次");
}
jiaoti = true;
this.notify();
}
}

打印结果可见,已经实现子线程和主线程有条不紊的交替运行,线程间既能互斥,同时又可以相互同步通信运行;线程的互斥是通过synchronized实现的,线程间的同步通信是两个线程间共同持有一个变量来实现的。但是线程有一个“假唤醒”的情况,虽然发生率低,但是我们不能忽略,继续调整代码:

public class ThreadSynchronizedTechnology {
public static void main(String[] args) {
final Songzl song = new Songzl();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=1;i<=50;i++){
song.sub(i);
}
}
}).start(); for(int i=1;i<=50;i++){
song.main(i);
}
}
}
class Songzl{
//实现线程同步通信,互斥的方法共享次变量
private boolean jiaoti = true;
//子线程运行的方法:同一个类中方法互斥,类似与synchronized(this){}
public synchronized void sub(int i) {
//避免线程的假唤醒
while(!jiaoti){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for(int j=1;j<=10;j++){
System.out.println("子线程:"+Thread.currentThread().getName()+"运行第"+i+"次,重复第"+j+"次");
}
jiaoti = false;
this.notify();
}
//主线程运行的方法:同一个类中方法互斥,类似与synchronized(this){}
public synchronized void main(int i){
//避免线程的假唤醒
while(jiaoti){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for(int j=1;j<=10;j++){
System.out.println("主线程:"+Thread.currentThread().getName()+"运行第"+i+"次,重复第"+j+"次");
}
jiaoti = true;
this.notify();
}
}

我们使用while循环来避免这种假唤醒的情况,当CPU任性的给与不该执行的线程、或者线程神经病的自己唤醒自己,我们可以使用while循环来避免上述情况。好了到此为止,代码已经完全满足我们的需求了。通过上面代码的循序渐进,我们很容易理解线程间的同步与互斥技术。

总结:线程之间的制约关系?

当线程并发执行时,由于资源共享和线程协作,使用线程之间会存在以下两种制约关系。

(1)间接相互制约。一个系统中的多个线程必然要共享某种系统资源,如共享CPU,共享I/O设备,所谓间接相互制约即源于这种资源共享,打印机就是最好的例子,线程A在使用打印机时,其它线程都要等待。

(2)直接相互制约。这种制约主要是因为线程之间的合作,如有线程A将计算结果提供给线程B作进一步处理,那么线程B在线程A将数据送达之前都将处于阻塞状态。

间接相互制约可以称为互斥,直接相互制约可以称为同步,对于互斥可以这样理解,线程A和线程B互斥访问某个资源则它们之间就会产个顺序问题——要么线程A等待线程B操作完毕,要么线程B等待线程操作完毕,这其实就是线程的同步了。因此同步包括互斥,互斥其实是一种特殊的同步

线程系列3--Java线程同步通信技术的更多相关文章

  1. 【java线程系列】java线程系列之线程间的交互wait()/notify()/notifyAll()及生产者与消费者模型

    关于线程,博主写过java线程详解基本上把java线程的基础知识都讲解到位了,但是那还远远不够,多线程的存在就是为了让多个线程去协作来完成某一具体任务,比如生产者与消费者模型,因此了解线程间的协作是非 ...

  2. 【java线程系列】java线程系列之java线程池详解

    一线程池的概念及为何需要线程池: 我们知道当我们自己创建一个线程时如果该线程执行完任务后就进入死亡状态,这样如果我们需要在次使用一个线程时得重新创建一个线程,但是线程的创建是要付出一定的代价的,如果在 ...

  3. Java并发编程系列-(7) Java线程安全

    7. 线程安全 7.1 线程安全的定义 如果多线程下使用这个类,不过多线程如何使用和调度这个类,这个类总是表示出正确的行为,这个类就是线程安全的. 类的线程安全表现为: 操作的原子性 内存的可见性 不 ...

  4. Java并发编程系列-(6) Java线程池

    6. 线程池 6.1 基本概念 在web开发中,服务器需要接受并处理请求,所以会为一个请求来分配一个线程来进行处理.如果每次请求都新创建一个线程的话实现起来非常简便,但是存在一个问题:如果并发的请求数 ...

  5. 线程系列08,实现线程锁的各种方式,使用lock,Montor,Mutex,Semaphore以及线程死锁

    当涉及到多线程共享数据,需要数据同步的时候,就可以考虑使用线程锁了.本篇体验线程锁的各种用法以及线程死锁.主要包括: ※ 使用lock处理数据同步※ 使用Monitor.Enter和Monitor.E ...

  6. 【阿里面试系列】Java线程的应用及挑战

    文章简介 上一篇文章[「阿里面试系列」搞懂并发编程,轻松应对80%的面试场景]我们了解了进程和线程的发展历史.线程的生命周期.线程的优势和使用场景,这一篇,我们从Java层面更进一步了解线程的使用.关 ...

  7. java线程系列之三(线程协作)

    本文来自:高爽|Coder,原文地址:http://blog.csdn.net/ghsau/article/details/7433673,转载请注明. 上一篇讲述了线程的互斥(同步),但是在很多情况 ...

  8. 并发编程系列:Java线程池的使用方式,核心运行原理、以及注意事项

    并发编程系列: 高并发编程系列:4种常用Java线程锁的特点,性能比较.使用场景 线程池的缘由 java中为了提高并发度,可以使用多线程共同执行,但是如果有大量线程短时间之内被创建和销毁,会占用大量的 ...

  9. 面试官系统精讲Java源码及大厂真题系列之Java线程安全的解决办法

    1. 背景 1.1 static修饰类变量.方法.方法块.  public + static = 该变量任何类都可以直接访问,而且无需初始化类,直接使用 类名.static 变量 1.2 多个线程同时 ...

  10. 线程之一:JAVA线程基础

    参考core java,马士兵视频 1.线程的基本概念 (1)一个线程是一个程序内部的顺序控制流. (2)线程和进程 –每个进程都有独立的代码和数据空间(进程上下文),进程切换的开销大. –线程:轻量 ...

随机推荐

  1. 【ES6 】声明变量的方式

    var function let const import class

  2. SQL的GROUP BY 与 Order By

    1.概述 “Group By”从字面意义上理解就是根据“By”指定的规则对数据进行分组,所谓的分组就是将一个“数据集”划分成若干个“小区域”,然后针对若干个“小区域”进行数据处理. 2.原始表 3.简 ...

  3. 剖析Vue之双向数据绑定

    vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调 ...

  4. ChinaCock打印控件介绍-TCCFujitsuPrinter实现蓝牙针式打印

    项目中遇到,要蓝牙针式打印机,用手机打印表单.感谢专家,对厂家提供的SDK进行了封装,实现利用Delphi开发出这一功能. 现在来看看,如何利用这一控件实现打印过程: procedure startS ...

  5. c#传入类名添加类对应的表数据

    添加方法: public int Insert<T>(T model) where T : class, new() { int sucess = 0; if (model is Temp ...

  6. java_day11_IO流

    第十一章:IO流 1.流的概念 流是个抽象的概念,是对输入输出设备的抽象,Java程序中,对于数据的输入/输出操作都是以"流"的方式进行.设备可以是文件,网络,内存等 流具有方向性 ...

  7. 简单粗暴 每个servlet之前都插入一段代码解决 乱码问题

    response.setHeader("content-type", "text/html;charset=UTF-8"); response.setChara ...

  8. Q&A(一)

    1.四种常见的无监督式任务? 聚类.可视化.降维.关联规则学习 2.什么是核外学习? 核外算法可以处理计算机主内存无法应对的大量数据.它将数据分割成小批量,然后使用在线学习技术从这些小批量中学习. 3 ...

  9. [uboot] (番外篇)uboot 驱动模型(转)重要

    [uboot] uboot流程系列:[project X] tiny210(s5pv210)上电启动流程(BL0-BL2)[project X] tiny210(s5pv210)从存储设备加载代码到D ...

  10. NoSQL数据库技术实战-第1章 NoSQL与大数据简介 NoSQL产生的原因

    NoSQL产生的原因: 关系型数据库不擅长的操作,是NoSQL应运而生的原因: 大量的数据写入操作书上写的是“大量数据的写入操作“,我理解的应该是“大量的数据写入操作”,因为大量的数据写入操作才会引起 ...