Java多线程学习(五)线程间通信知识点补充
系列文章传送门:
Java多线程学习(二)synchronized关键字(1)
Java多线程学习(二)synchronized关键字(2)
Java多线程学习(四)等待/通知(wait/notify)机制
系列文章将被优先更新于微信公众号“Java面试通关手册”,欢迎广大Java程序员和爱好技术的人员关注。
本节思维导图:
思维导图源文件+思维导图软件关注微信公众号:“Java面试通关手册” 回复关键字:“Java多线程” 免费领取。
我们通过之前几章的学习已经知道在线程间通信用到的synchronized关键字、volatile关键字以及等待/通知(wait/notify)机制。今天我们就来讲一下线程间通信的其他知识点:管道输入/输出流、Thread.join()的使用、ThreadLocal的使用。
一 管道输入/输出流
管道输入/输出流和普通文件的输入/输出流或者网络输入、输出流不同之处在于管道输入/输出流主要用于线程之间的数据传输,而且传输的媒介为内存。
管道输入/输出流主要包括下列两类的实现:
面向字节: PipedOutputStream、 PipedInputStream
面向字符: PipedWriter、 PipedReader
1.1 第一个管道输入/输出流实例
完整代码:https://github.com/Snailclimb/threadDemo/tree/master/src/pipedInputOutput
writeMethod方法
public void writeMethod(PipedOutputStream out) {
try {
System.out.println("write :");
for (int i = 0; i < 300; i++) {
String outData = "" + (i + 1);
out.write(outData.getBytes());
System.out.print(outData);
}
System.out.println();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
readMethod方法
public void readMethod(PipedInputStream input) {
try {
System.out.println("read :");
byte[] byteArray = new byte[20];
int readLength = input.read(byteArray);
while (readLength != -1) {
String newData = new String(byteArray, 0, readLength);
System.out.print(newData);
readLength = input.read(byteArray);
}
System.out.println();
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
测试方法
public static void main(String[] args) {
try {
WriteData writeData = new WriteData();
ReadData readData = new ReadData();
PipedInputStream inputStream = new PipedInputStream();
PipedOutputStream outputStream = new PipedOutputStream();
// inputStream.connect(outputStream);
outputStream.connect(inputStream);
ThreadRead threadRead = new ThreadRead(readData, inputStream);
threadRead.start();
Thread.sleep(2000);
ThreadWrite threadWrite = new ThreadWrite(writeData, outputStream);
threadWrite.start();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
我们上面定义了两个方法writeMethod和readMethod,前者用于写字节/字符(取决于你用的是PipedOuputStream还是PipedWriter),后者用于读取字节/字符(取决于你用的是PipedInputStream还是PipedReader).我们定义了两个线程threadRead和threadWrite ,threadRead线程运行readMethod方法,threadWrite运行writeMethod方法。然后 通过outputStream.connect(inputStream)或inputStream.connect(outputStream)使两个管道流产生链接,这样就可以将数据进行输入与输出了。
运行结果:
二 Thread.join()的使用
在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。另外,一个线程需要等待另一个线程也需要用到join()方法。
Thread类除了提供join()方法之外,还提供了join(long millis)、join(long millis, int nanos)两个具有超时特性的方法。这两个超时方法表示,如果线程thread在指定的超时时间没有终止,那么将会从该超时方法中返回。
2.1 join方法使用
不使用join方法的弊端演示:
Test.java
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread threadTest = new MyThread();
threadTest.start();
//Thread.sleep(?);//因为不知道子线程要花的时间这里不知道填多少时间
System.out.println("我想当threadTest对象执行完毕后我再执行");
}
static public class MyThread extends Thread {
@Override
public void run() {
System.out.println("我想先执行");
}
}
}
运行结果:
可以看到子线程中后被执行,这里的例子只是一个简单的演示,我们想一下:假如子线程运行的结果被主线程运行需要怎么办? sleep方法? 当然可以,但是子线程运行需要的时间是不确定的,所以sleep多长时间当然也就不确定了。这里就需要使用join方法解决上面的问题。
使用join方法解决上面的问题:
Test.java
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread threadTest = new MyThread();
threadTest.start();
//Thread.sleep(?);//因为不知道子线程要花的时间这里不知道填多少时间
threadTest.join();
System.out.println("我想当threadTest对象执行完毕后我再执行");
}
static public class MyThread extends Thread {
@Override
public void run() {
System.out.println("我想先执行");
}
}
}
上面的代码仅仅加上了一句:threadTest.join();。在这里join方法的作用就是主线程需要等待子线程执行完成之后再结束。
2.2 join(long millis)方法的使用
join(long millis)中的参数就是设定的等待时间。
JoinLongTest.java
public class JoinLongTest {
public static void main(String[] args) {
try {
MyThread threadTest = new MyThread();
threadTest.start();
threadTest.join(2000);// 只等2秒
//Thread.sleep(2000);
System.out.println(" end timer=" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static public class MyThread extends Thread {
@Override
public void run() {
try {
System.out.println("begin Timer=" + System.currentTimeMillis());
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
不管是运行threadTest.join(2000)还是Thread.sleep(2000), “end timer=1522036620288”语句的输出都是间隔两秒,“end timer=1522036620288”语句输出后该程序还会运行一段时间,因为线程中的run方法中有Thread.sleep(10000)语句。
另外threadTest.join(2000) 和Thread.sleep(2000) 和区别在于: Thread.sleep(2000)不会释放锁,threadTest.join(2000)会释放锁 。
三 ThreadLocal的使用
变量值的共享可以使用public static变量的形式,所有线程都使用一个public static变量。如果想实现每一个线程都有自己的共享变量该如何解决呢?JDK中提供的ThreadLocal类正是为了解决这样的问题。ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。
再举个简单的例子:
比如有两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么ThreadLocal就是用来这两个线程竞争的。
ThreadLocal类相关方法:
方法名称 | 描述 |
---|---|
get() | 返回当前线程的此线程局部变量的副本中的值。 |
set(T value) | 将当前线程的此线程局部变量的副本设置为指定的值 |
remove() | 删除此线程局部变量的当前线程的值。 |
initialValue() | 返回此线程局部变量的当前线程的“初始值” |
3.1 ThreadLocal类的初试
Test1.java
public class Test1 {
public static ThreadLocal<String> t1 = new ThreadLocal<String>();
public static void main(String[] args) {
if (t1.get() == null) {
System.out.println("为ThreadLocal类对象放入值:aaa");
t1.set("aaaֵ");
}
System.out.println(t1.get());//aaa
System.out.println(t1.get());//aaa
}
}
从运行结果可以看出,第一次调用ThreadLocal对象的get()方法时返回的值是null,通过调用set()方法可以为ThreadLocal对象赋值。
如果想要解决get()方法null的问题,可以使用ThreadLocal对象的initialValue方法。如下:
Test2.java
public class Test2 {
public static ThreadLocalExt t1 = new ThreadLocalExt();
public static void main(String[] args) {
if (t1.get() == null) {
System.out.println("从未放过值");
t1.set("我的值");
}
System.out.println(t1.get());
System.out.println(t1.get());
}
static public class ThreadLocalExt extends ThreadLocal {
@Override
protected Object initialValue() {
return "我是默认值 第一次get不再为null";
}
}
}
3.2 验证线程变量间的隔离性
Test3.java
/**
*TODO 验证线程变量间的隔离性
*/
public class Test3 {
public static void main(String[] args) {
try {
for (int i = 0; i < 10; i++) {
System.out.println(" 在Main线程中取值=" + Tools.tl.get());
Thread.sleep(100);
}
Thread.sleep(5000);
ThreadA a = new ThreadA();
a.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static public class Tools {
public static ThreadLocalExt tl = new ThreadLocalExt();
}
static public class ThreadLocalExt extends ThreadLocal {
@Override
protected Object initialValue() {
return new Date().getTime();
}
}
static public class ThreadA extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
System.out.println("在ThreadA线程中取值=" + Tools.tl.get());
Thread.sleep(100);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
从运行结果可以看出子线程和父线程各自拥有各自的值。
运行结果:
3.3 InheritableThreadLocal
ThreadLocal类固然很好,但是子线程并不能取到父线程的ThreadLocal类的变量,InheritableThreadLocal类就是解决这个问题的。
取父线程的值:
修改Test3.java的内部类Tools 和ThreadLocalExt类如下:
static public class Tools {
public static InheritableThreadLocalExt tl = new InheritableThreadLocalExt();
}
static public class InheritableThreadLocalExt extends InheritableThreadLocal {
@Override
protected Object initialValue() {
return new Date().getTime();
}
}
运行结果:
取父线程的值并修改:
修改Test3.java的内部类Tools 和InheritableThreadLocalExt类如下:
static public class Tools {
public static InheritableThreadLocalExt tl = new InheritableThreadLocalExt();
}
static public class InheritableThreadLocalExt extends InheritableThreadLocal {
@Override
protected Object initialValue() {
return new Date().getTime();
}
@Override
protected Object childValue(Object parentValue) {
return parentValue + " 我在子线程加的~!";
}
}
运行结果:
在使用InheritableThreadLocal类需要注意的一点是:如果子线程在取得值的同时,主线程将InheritableThreadLocal中的值进行更改,那么子线程取到的还是旧值。
参考:
《Java多线程编程核心技术》
《Java并发编程的艺术》
如果你觉得博主的文章不错,欢迎转发点赞。你能从中学到知识就是我最大的幸运。
欢迎关注我的微信公众号:“Java面试通关手册”(分享各种Java学习资源,面试题,以及企业级Java实战项目回复关键字免费领取)。另外我创建了一个Java学习交流群(群号:174594747),欢迎大家加入一起学习,这里更有面试,学习视频等资源的分享。
Java多线程学习(五)线程间通信知识点补充的更多相关文章
- Java多线程编程(6)--线程间通信(下)
因为本文的内容大部分是以生产者/消费者模式来进行讲解和举例的,所以在开始学习本文介绍的几种线程间的通信方式之前,我们先来熟悉一下生产者/消费者模式. 在实际的软件开发过程中,经常会碰到如下场景 ...
- Java多线程编程核心技术---线程间通信(一)
线程是操作系统中独立的个体,但这些个体如果不经过特殊处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一.线程间通信可以使系统之间的交互性更强大,在大大提高CPU利用率的同时还会使程序员对各 ...
- Java多线程编程核心技术---线程间通信(二)
通过管道进行线程间通信:字节流 Java提供了各种各样的输入/输出流Stream可以很方便地对数据进行操作,其中管道流(pipeStream)是一种特殊的流,用于在不同线程间直接传送数据,一个线程发送 ...
- java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()(多线程编程之二)
本篇我们将讨论以下知识点: 1.线程同步问题的产生 什么是线程同步问题,我们先来看一段卖票系统的代码,然后再分析这个问题: package com.zejian.test; /** * @author ...
- Java多线程编程(5)--线程间通信
一.等待与通知 某些情况下,程序要执行的操作需要满足一定的条件(下文统一将其称之为保护条件)才能执行.在单线程编程中,我们可以使用轮询的方式来实现,即频繁地判断是否满足保护条件,若不满足则继续判断 ...
- java多线程5:线程间的通信
在多线程系统中,彼此之间的通信协作非常重要,下面来聊聊线程间通信的几种方式. wait/notify 想像一个场景,A.B两个线程操作一个共享List对象,A对List进行add操作,B线程等待Lis ...
- Java学习:线程间通信
线程间通信 概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同重点:有效的利用资源 分析:需要那些类 1 资源类:包子类 设置包子的属性 包子的状态:有true 没有false 2 ...
- Java 里如何实现线程间通信
正常情况下,每个子线程完成各自的任务就可以结束了.不过有的时候,我们希望多个线程协同工作来完成某个任务,这时就涉及到了线程间通信了. 本文涉及到的知识点:thread.join(), object.w ...
- Java笔记(二十)……线程间通信
概述 当需要多线程配合完成一项任务时,往往需要用到线程间通信,以确保任务的稳步快速运行 相关语句 wait():挂起线程,释放锁,相当于自动放弃了执行权限 notify():唤醒wait等待队列里的第 ...
随机推荐
- Oracle基础 表分区
Oracle基础 表分区 一.表分区 (一)表分区的分类 1.范围分区(range) 2.散列分区(hash) 3.列表分区(list) 4.复合分区:范围-哈希(range-hash).范围-列表( ...
- 【bzoj1507】[NOI2003]Editor /【bzoj1269】[AHOI2006]文本编辑器editor Splay
[bzoj1507][NOI2003]Editor 题目描述 输入 输入文件editor.in的第一行是指令条数t,以下是需要执行的t个操作.其中: 为了使输入文件便于阅读,Insert操作的字符串中 ...
- Flask的第一个应用
Flask 是一个 Python 实现的 Web 开发微框架,微框架中的“微”意味着 Flask 旨在保持核心简单而易于扩展. 与Django功能上比较: Django:中间件,路由系统,视图(CBV ...
- hihoCoder#1698 : 假期计划 组合数
题面:hihoCoder#1698 : 假期计划 组合数 题解: 题目要求是有序的排列,因此我们可以在一开始就乘上A!*B!然后在把这个序列划分成很多段. 这样的话由于乘了阶乘,所以所有排列我们都已 ...
- CF578C:Weakness and Poorness——题解
https://vjudge.net/problem/CodeForces-578C —————————————————————————— 题目大意:序列的数-x,求最大连续子序列和的绝对值的最小值. ...
- BZOJ4873 [Shoi2017]寿司餐厅 【最大权闭合子图】
题目链接 BZOJ4873 题解 题意很鬼畜,就可以考虑网络流[雾] 然后就会发现这是一个裸的最大权闭合子图 就是注意要离散化一下代号 #include<algorithm> #inclu ...
- POJ1679:The Unique MST(最小生成树)
The Unique MST Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 38430 Accepted: 14045 ...
- HDU2819:Swap(二分图匹配)
Swap Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submis ...
- 不要在linux上启用net.ipv4.tcp_tw_recycle参数
不要在linux上启用net.ipv4.tcp_tw_recycle参数 发布于 2015/07/27 莿鸟栖草堂 本文为翻译英文BLOG<Coping with the TCP TIME-WA ...
- redis 选择数据库
redis 127.0.0.1:6379> SELECT 1 # 使用 1 号数据库