系列文章传送门:java
Java多线程学习(二)synchronized关键字(1)程序员
java多线程学习(二)synchronized关键字(2) github
Java多线程学习(四)等待/通知(wait/notify)机制编程
系列文章将被优先更新于微信公众号<font color="red">“Java面试通关手册”</font>,欢迎广大Java程序员和爱好技术的人员关注。并发
本节思惟导图:
思惟导图源文件+思惟导图软件关注微信公众号:“Java面试通关手册” 回复关键字:“Java多线程” 免费领取。
咱们经过以前几章的学习已经知道在<font color="red">线程间通讯</font>用到的<font color="red">synchronized关键字、volatile关键字以及等待/通知(wait/notify)机制</font>。今天咱们就来说一下线程间通讯的其余知识点:<font color="red">管道输入/输出流、Thread.join()的使用、ThreadLocal的使用。</font>
管道输入/输出流和普通文件的输入/输出流或者网络输入、输出流不一样之处在于管道输入/输出流主要用于线程之间的数据传输,并且传输的媒介为内存。
管道输入/输出流主要包括下列两类的实现:
面向字节: PipedOutputStream、 PipedInputStream
面向字符: PipedWriter、 PipedReader
完整代码:https://github.com/Snailclimb/threadDemo/tree/master/src/pipedInputOutput
<font size="2">writeMethod方法</font>
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(); } }
<font size="2">readMethod方法</font>
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(); } }
<font size="2">测试方法</font>
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)使两个管道流产生连接,这样就能够将数据进行输入与输出了。
<font size="2">运行结果:</font>
在不少状况下,主线程生成并起动了子线程,若是子线程里要进行大量的耗时的运算,主线程每每将于子线程以前结束,可是若是主线程处理完其余的事务后,须要用到子线程的处理结果,也就是<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>。
不使用join方法的弊端演示:
<font size="2">Test.java</font>
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("我想先执行"); } } }
<font size="2">运行结果:</font>
能够看到子线程中后被执行,这里的例子只是一个简单的演示,咱们想一下:假如子线程运行的结果被主线程运行须要怎么办? sleep方法? 固然能够,可是子线程运行须要的时间是不肯定的,因此sleep多长时间固然也就不肯定了。这里就须要使用join方法解决上面的问题。
使用join方法解决上面的问题:
<font size="2">Test.java</font>
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方法的做用就是主线程须要等待子线程执行完成以后再结束。
join(long millis)中的参数就是设定的等待时间。
<font size="2">JoinLongTest.java</font>
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(); } } } }
<font size="2">运行结果:</font>
不论是运行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>。
变量值的共享可使用public static变量的形式,全部线程都使用一个public static变量。<font color="red">若是想实现每个线程都有本身的共享变量该如何解决呢?</font>JDK中提供的<font color="red">ThreadLocal类</font>正是为了解决这样的问题。ThreadLocal类主要解决的就是让每一个线程绑定本身的值,能够将ThreadLocal类形象的比喻成存放数据的盒子,盒子中能够存储每一个线程的私有数据。
再举个简单的例子:
好比有两我的去宝屋收集宝物,这两个共用一个袋子的话确定会产生争执,可是给他们两我的每一个人分配一个袋子的话就不会出现这样的问题。若是把这两我的比做线程的话,那么ThreadLocal就是用来这两个线程竞争的。
ThreadLocal类相关方法:
| 方法名称 | 描述 |
| :-------- | --------:|
| get() | 返回当前线程的此线程局部变量的副本中的值。 |
| set(T value) | 将当前线程的此线程局部变量的副本设置为指定的值 |
| remove() | 删除此线程局部变量的当前线程的值。|
| initialValue() | 返回此线程局部变量的当前线程的“初始值” |
<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()); System.out.println(t1.get()); } }
从运行结果能够看出,第一次调用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"; } } }
<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>
<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实战项目回复关键字免费领取)。另外我建立了一个Java学习交流群(群号:174594747),欢迎你们加入一块儿学习,这里更有面试,学习视频等资源的分享。