Java多线程学习(五)线程间通信知识点补充

我自己总结的Java学习的系统知识点以及面试问题,目前已经开源,会一直完善下去,欢迎建议和指导欢迎Star: https://github.com/Snailclimb/Java-Guide

本节思维导图:

本节思维导图

我们通过之前几章的学习已经知道在线程间通信用到的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();
		}

	}

我们上面定义了两个方法writeMethodreadMethod,前者用于写字节/字符(取决于你用的是PipedOuputStream还是PipedWriter),后者用于读取字节/字符(取决于你用的是PipedInputStream还是PipedReader).我们定义了两个线程threadReadthreadWrite ,threadRead线程运行readMethod方法,threadWrite运行writeMethod方法。然后 通过outputStream.connect(inputStream)inputStream.connect(outputStream)使两个管道流产生链接,这样就可以将数据进行输入与输出了。

运行结果:

运行结果

二 Thread.join()的使用

在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是<font color="red">主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。另外,一个线程需要等待另一个线程也需要用到join()方法</font>。

Thread类除了提供<font color="red">join()方法</font>之外,还提供了<font color="red">join(long millis)、join(long millis, int nanos)</font>两个具有超时特性的方法。<font color="red">这两个超时方法表示,如果线程thread在指定的超时时间没有终止,那么将会从该超时方法中返回</font>。

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) 和区别在于:<font color="red"> Thread.sleep(2000)不会释放锁,threadTest.join(2000)会释放锁 </font>。

三 ThreadLocal的使用

变量值的共享可以使用public static变量的形式,所有线程都使用一个public static变量。<font color="red">如果想实现每一个线程都有自己的共享变量该如何解决呢?</font>JDK中提供的<font color="red">ThreadLocal类</font>正是为了解决这样的问题。ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。

再举个简单的例子:

比如有两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么ThreadLocal就是用来这两个线程竞争的。

ThreadLocal类相关方法:

方法名称

描述

get()

返回当前线程的此线程局部变量的副本中的值。

set(T value)

将当前线程的此线程局部变量的副本设置为指定的值

remove()

删除此线程局部变量的当前线程的值。

initialValue()

返回此线程局部变量的当前线程的“初始值”

3.1 ThreadLocal类的初试

<font size="2">Test1.java</font>

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对象的<font color="red">get()方法</font>时返回的值是null,通过调用<font color="red">set()方法</font>可以为ThreadLocal对象赋值。

如果想要解决get()方法null的问题,可以使用ThreadLocal对象的<font color="red">initialValue方法</font>。如下:

<font size="2">Test2.java</font>

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 验证线程变量间的隔离性

<font size="2">Test3.java</font>

/**
 *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();
			}
		}

	}
}

从运行结果可以看出子线程和父线程各自拥有各自的值。

<font size="2">运行结果:</font>

运行结果

3.3 InheritableThreadLocal

<font color="red">ThreadLocal类固然很好,但是子线程并不能取到父线程的ThreadLocal类的变量,InheritableThreadLocal类就是解决这个问题的</font>。

<font color="red">取父线程的值:</font>

修改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();
		}
	}

<font size="2">运行结果:</font>

运行结果

<font color="red">取父线程的值并修改:</font>

修改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 + " 我在子线程加的~!";
		}
	}

<font size="2">运行结果:</font>

运行结果

在使用InheritableThreadLocal类需要注意的一点是:<font color="red">如果子线程在取得值的同时,主线程将InheritableThreadLocal中的值进行更改,那么子线程取到的还是旧值</font>。

参考:

《Java多线程编程核心技术》

《Java并发编程的艺术》

如果你觉得博主的文章不错,欢迎转发点赞。你能从中学到知识就是我最大的幸运。

欢迎关注我的微信公众号:“Java面试通关手册”(分享各种Java学习资源,面试题,以及企业级Java实战项目回复关键字免费领取)。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏余林丰

14.Java中的Future模式

jdk1.7.0_79   本文实际上是对上文《13.ThreadPoolExecutor线程池之submit方法》的一个延续或者一个补充。在上文中提到的su...

2538
来自专栏大数据学习笔记

Spark2.x学习笔记:10、简易电影受众系统

10、 简易电影受众系统 本章内容,源码参考了https://github.com/XichengDong/simplespark 10.1 数据准备 (1)下...

2879
来自专栏Java3y

Java锁机制了解一下

2116
来自专栏Java架构沉思录

Java中如何提升锁性能

比如100个人去银行办理业务,要填一百张表,但是只有一支笔,那么很显然,每个人用笔的时间越短,效率也就越高。看代码:

512
来自专栏java一日一条

Java 并发开发:内置锁 Synchronized

在多线程编程中,线程安全问题是一个最为关键的问题,其核心概念就在于正确性,即当多个线程访问某一共享、可变数据时,始终都不会导致数据破坏以及其他不该出现的结果。而...

392
来自专栏java达人

多线程设计模式解读1—Guarded Suspension(保护性暂挂模式)

大家好,今天我们给大家介绍一个多线程设计模式的一个概念,我们平时业务代码写得比较多,因此,如果刚上手写比较复杂多线程代码,很有可能会埋下一些坑,而这些坑一时之间...

744
来自专栏技术/开源

.net源码分析 - ConcurrentDictionary<TKey, TValue>

List源码分析 Dictionary源码分析 ConcurrentDictionary源码分析 继上篇Dictionary源码分析,上篇讲过的在这里不会再...

2005
来自专栏Java帮帮-微信公众号-技术文章全总结

Java多线程详解2

Java多线程详解 Java线程:线程的同步与锁 一、同步问题提出 线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。 例如:两个线程Threa...

3297
来自专栏程序猿DD

基于Consul的分布式锁实现

我们在构建分布式系统的时候,经常需要控制对共享资源的互斥访问。这个时候我们就涉及到分布式锁(也称为全局锁)的实现,基于目前的各种工具,我们已经有了大量的实现方式...

2585
来自专栏大数据架构

Java进阶(三)多线程开发关键技术

其实这个问题应该这么问——sleep和wait有什么相同点。因为这两个方法除了都能让当前线程暂停执行完,几乎没有其它相同点。

34318

扫码关注云+社区