精选文章 面试官:说一下你对Java线程之间通信方式的理解

面试官:说一下你对Java线程之间通信方式的理解

作者:架构师_橘子 时间: 2021-02-05 09:43:13
架构师_橘子 2021-02-05 09:43:13
【摘要】多线程和并发,在平时开发中有些小伙伴用的不多,但是有些工作经验的面试中还是容易被问到的,故在之后几期先整理一些常见的多线程面试题供参考。 
通信方式 
 ①同步  ②while轮询的方式  ③wait/notify机制  ④管道通信 
一,介绍 
本文总结我对于JAVA多线程中线程之间的通信方式的理解,主要以代码结合文字的方式来讨论线程间的通信,故摘抄了书中的一些示例代码。 
二,线程间的通...

多线程和并发,在平时开发中有些小伙伴用的不多,但是有些工作经验的面试中还是容易被问到的,故在之后几期先整理一些常见的多线程面试题供参考。

通信方式

  • ①同步

  • ②while轮询的方式

  • ③wait/notify机制

  • ④管道通信

一,介绍

本文总结我对于JAVA多线程中线程之间的通信方式的理解,主要以代码结合文字的方式来讨论线程间的通信,故摘抄了书中的一些示例代码。

二,线程间的通信方式

①同步

这里讲的同步是指多个线程通过synchronized关键字这种方式来实现线程间的通信。

参考示例:

public class MyObject { synchronized public void methodA() { //do something.... } synchronized public void methodB() { //do some other thing }
}

public class ThreadA extends Thread { private MyObject object;
//省略构造方法 @Override public void run() { super.run(); object.methodA(); }
}

public class ThreadB extends Thread { private MyObject object;
//省略构造方法 @Override public void run() { super.run(); object.methodB(); }
}

public class Run { public static void main(String[] args) { MyObject object = new MyObject(); //线程A与线程B 持有的是同一个对象:object ThreadA a = new ThreadA(object); ThreadB b = new ThreadB(object); a.start(); b.start(); }
}

由于线程A和线程B持有同一个MyObject类的对象object,尽管这两个线程需要调用不同的方法,但是它们是同步执行的,比如:线程B需要等待线程A执行完了methodA()方法之后,它才能执行methodB()方法。这样,线程A和线程B就实现了 通信。

这种方式,本质上就是“共享内存”式的通信。多个线程需要访问同一个共享变量,谁拿到了锁(获得了访问权限),谁就可以执行。

②while轮询的方式

代码如下:

import java.util.ArrayList;
import java.util.List;

public class MyList { private List list = new ArrayList(); public void add() { list.add("elements"); } public int size() { return list.size(); }
}

import mylist.MyList;

public class ThreadA extends Thread { private MyList list; public ThreadA(MyList list) { super(); this.list = list; } @Override public void run() { try { for (int i = 0; i < 10; i++) { list.add(); System.out.println("添加了" + (i + 1) + "个元素"); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } }
}

import mylist.MyList;

public class ThreadB extends Thread { private MyList list; public ThreadB(MyList list) { super(); this.list = list; } @Override public void run() { try { while (true) { if (list.size() == 5) { System.out.println("==5, 线程b准备退出了"); throw new InterruptedException(); } } } catch (InterruptedException e) { e.printStackTrace(); } }
}

import mylist.MyList;
import extthread.ThreadA;
import extthread.ThreadB;

public class Test { public static void main(String[] args) { MyList service = new MyList(); ThreadA a = new ThreadA(service); a.setName("A"); a.start(); ThreadB b = new ThreadB(service); b.setName("B"); b.start(); }
}

在这种方式下,线程A不断地改变条件,线程ThreadB不停地通过while语句检测这个条件(list.size()==5)是否成立 ,从而实现了线程间的通信。但是这种方式会浪费CPU资源。

之所以说它浪费资源,是因为JVM调度器将CPU交给线程B执行时,它没做啥“有用”的工作,只是在不断地测试 某个条件是否成立。就类似于现实生活中,某个人一直看着手机屏幕是否有电话来了,而不是:在干别的事情,当有电话来时,响铃通知TA电话来了。

这种方式还存在另外一个问题:

轮询的条件的可见性问题,关于内存可见性问题,可参考:JAVA多线程之volatile 与 synchronized的比较中的第一点“一,volatile关键字的可见性”

http://www.cnblogs.com/hapjin/p/5492880.html

线程都是先把变量读取到本地线程栈空间,然后再去再去修改的本地变量。因此,如果线程B每次都在取本地的 条件变量,那么尽管另外一个线程已经改变了轮询的条件,它也察觉不到,这样也会造成死循环。

③wait/notify机制

代码如下:

import java.util.ArrayList;
import java.util.List;

public class MyList { private static List list = new ArrayList(); public static void add() { list.add("anyString"); } public static int size() { return list.size(); }
}


public class ThreadA extends Thread { private Object lock; public ThreadA(Object lock) { super(); this.lock = lock; } @Override public void run() { try { synchronized (lock) { if (MyList.size() != 5) { System.out.println("wait begin " + System.currentTimeMillis()); lock.wait(); System.out.println("wait end  " + System.currentTimeMillis()); } } } catch (InterruptedException e) { e.printStackTrace(); } }
}


public class ThreadB extends Thread { private Object lock; public ThreadB(Object lock) { super(); this.lock = lock; } @Override public void run() { try { synchronized (lock) { for (int i = 0; i < 10; i++) { MyList.add(); if (MyList.size() == 5) { lock.notify(); System.out.println("已经发出了通知"); } System.out.println("添加了" + (i + 1) + "个元素!"); Thread.sleep(1000); } } } catch (InterruptedException e) { e.printStackTrace(); } }
}

public class Run { public static void main(String[] args) { try { Object lock = new Object(); ThreadA a = new ThreadA(lock); a.start(); Thread.sleep(50); ThreadB b = new ThreadB(lock); b.start(); } catch (InterruptedException e) { e.printStackTrace(); } }
}

线程A要等待某个条件满足时(list.size()==5),才执行操作。线程B则向list中添加元素,改变list 的size。

A,B之间如何通信的呢?也就是说,线程A如何知道 list.size() 已经为5了呢?

这里用到了Object类的 wait() 和 notify() 方法。

当条件未满足时(list.size() !=5),线程A调用wait() 放弃CPU,并进入阻塞状态。---不像②while轮询那样占用CPU

当条件满足时,线程B调用 notify()通知 线程A,所谓通知线程A,就是唤醒线程A,并让它进入可运行状态。

这种方式的一个好处就是CPU的利用率提高了。

但是也有一些缺点:比如,线程B先执行,一下子添加了5个元素并调用了notify()发送了通知,而此时线程A还执行;当线程A执行并调用wait()时,那它永远就不可能被唤醒了。因为,线程B已经发了通知了,以后不再发通知了。这说明:通知过早,会打乱程序的执行逻辑。

④管道通信

就是使用java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信

具体就不介绍了。分布式系统中说的两种通信机制:共享内存机制和消息通信机制。感觉前面的①中的synchronized关键字和②中的while轮询 “属于” 共享内存机制,由于是轮询的条件使用了volatile关键字修饰时,这就表示它们通过判断这个“共享的条件变量“是否改变了,来实现进程间的交流。

而管道通信,更像消息传递机制,也就是说:通过管道,将一个线程中的消息发送给另一个。

勿删,copyright占位
分享文章到微博
分享文章到朋友圈

上一篇:Java中final,finalize和finally的区别

下一篇:美团架构师带你深入理解Nginx模块开发与架构解析(第2版)

您可能感兴趣

  • Java程序员面试中的多线程问题

    2013-3-5 16:32| 发布者: sxwgf| 查看: 1735| 评论: 0|来自: 伯乐在线  http://web.itivy.com/article-184-1.html     很多核心JAVA面试题来源于多线程(Multi-Threading)和集合框架(Collections Framework),理解核心线程概念时...

  • 记不住但是实用的java方法

    一、字符串转换方法     1、toString,需要保证调用这个方法的类、方法、变量不为null,否则会报空指针。    2、String.valueOf。这个方法在使用的时候是有些特殊的。一般情况下,如果是确定类型的null传入,返回的是字符         串“null”,而如果直接传入null,则会发生错误     3、(String) 字符...

  • Java泛型---入门

    为什么要引入泛型--->定义输入对象类型,方便使用、安全     1)对于一个Student类,存放一个Object对象,向内部存储数据,使用以下方法:     没有泛型的数据存储与读取     创建一个PO类(JavaBean),实体化对象,存储数据的类 /** * PO类(实体化对象,用于存储数据的类),JavaBean * @author ...

  • 【java多线程】---线程创建、start、run

    线程创建、start、run   一、创建线程方式      java创建线程的方式,主要有三种:类Thread、接口Runnable、接口Callable。 1、Thread和Runnable进行比较 他们之间的区别  1、实现Runnable的类更具有健壮性,避免了单继承的局限。  2、Runnable更容易实现资源共享,能多个线程同时处理一个资...

  • Java爬虫实践:Jsoup+HttpUnit爬取今日头条、网易、搜狐、凤凰新闻

    0x0 背景 最近学习爬虫,分析了几种主流的爬虫框架,决定使用最原始的两大框架进行练手:  Jsoup&HttpUnit  其中jsoup可以获取静态页面,并解析页面标签,最主要的是,可以采用类似于jquery的语法获取想要的标签元素,例如: //1.获取url地址的网页html html = Jsoup.con...

  • Java中的方法

    在其他语言中,方法称为过程或函数。带返回值的方法称为函数,返回值类型为void的方法称为过程。 定义方法 public static int max(int num1,int num2){ return 0;} public static 为修饰符 int 为返回值类型 max 为方法名 int num1 为形式参数 int num1,int num...

  • Java容器---MyMap02

    鉴于使用数组实现Map存在的一些问题,引入HashCode,实现HashMap的一些功能 /** * Map编程优化 * Map的底层实现为数组+链表 * 1.提高查询效率,避免循环遍历,使用Hashcode快速定位存储位置 * * Hashcode与equals的关系 * 1.equals相同的对象必然存在拥有相同的Hashcode值...

  • Java NIO 与 IO之间的区别及NIO的使用

    概述 Java NIO提供了与标准IO不同的IO工作方式:  Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,而NIO是基于通...

CSDN

CSDN

中国开发者社区CSDN (Chinese Software Developer Network) 创立于1999年,致力为中国开发者提供知识传播、在线学习、职业发展等全生命周期服务。

华为云40多款云服务产品0元试用活动

免费套餐,马上领取!
面试官:说一下你对Java线程之间通信方式的理解介绍:华为云为您免费提供面试官:说一下你对Java线程之间通信方式的理解在博客、论坛、帮助中心等栏目的相关文章,同时还可以通过 站内搜索 查询更多面试官:说一下你对Java线程之间通信方式的理解的相关内容。| 移动地址: 面试官:说一下你对Java线程之间通信方式的理解 | 写博客