系列文章傳送門: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),歡迎你們加入一塊兒學習,這裏更有面試,學習視頻等資源的分享。