摘要:我們通過之前幾章的學習已經(jīng)知道在線程間通信用到的關鍵字關鍵字以及等待通知機制。今天我們就來講一下線程間通信的其他知識點管道輸入輸出流的使用的使用。將當前線程的此線程局部變量的副本設置為指定的值刪除此線程局部變量的當前線程的值。
系列文章傳送門:
Java多線程學習(一)Java多線程入門
Java多線程學習(二)synchronized關鍵字(1)
java多線程學習(二)synchronized關鍵字(2)
Java多線程學習(三)volatile關鍵字
Java多線程學習(四)等待/通知(wait/notify)機制
Java多線程學習(五)線程間通信知識點補充
Java多線程學習(六)Lock鎖的使用
Java多線程學習(七)并發(fā)編程中一些問題
系列文章將被優(yōu)先更新于微信公眾號“Java面試通關手冊”,歡迎廣大Java程序員和愛好技術的人員關注。
本節(jié)思維導圖:
思維導圖源文件+思維導圖軟件關注微信公眾號:“Java面試通關手冊” 回復關鍵字:“Java多線程” 免費領取。
我們通過之前幾章的學習已經(jīng)知道在線程間通信用到的synchronized關鍵字、volatile關鍵字以及等待/通知(wait/notify)機制。今天我們就來講一下線程間通信的其他知識點:管道輸入/輸出流、Thread.join()的使用、ThreadLocal的使用。
一 管道輸入/輸出流管道輸入/輸出流和普通文件的輸入/輸出流或者網(wǎng)絡輸入、輸出流不同之處在于管道輸入/輸出流主要用于線程之間的數(shù)據(jù)傳輸,而且傳輸?shù)拿浇闉?strong>內存。
管道輸入/輸出流主要包括下列兩類的實現(xiàn):
面向字節(jié): 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,前者用于寫字節(jié)/字符(取決于你用的是PipedOuputStream還是PipedWriter),后者用于讀取字節(jié)/字符(取決于你用的是PipedInputStream還是PipedReader).我們定義了兩個線程threadRead和threadWrite ,threadRead線程運行readMethod方法,threadWrite運行writeMethod方法。然后 通過outputStream.connect(inputStream)或inputStream.connect(outputStream)使兩個管道流產(chǎn)生鏈接,這樣就可以將數(shù)據(jù)進行輸入與輸出了。
運行結果:
在很多情況下,主線程生成并起動了子線程,如果子線程里要進行大量的耗時的運算,主線程往往將于子線程之前結束,但是如果主線程處理完其他的事務后,需要用到子線程的處理結果,也就是主線程需要等待子線程執(zhí)行完成之后再結束,這個時候就要用到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對象執(zhí)行完畢后我再執(zhí)行"); } static public class MyThread extends Thread { @Override public void run() { System.out.println("我想先執(zhí)行"); } } }
運行結果:
可以看到子線程中后被執(zhí)行,這里的例子只是一個簡單的演示,我們想一下:假如子線程運行的結果被主線程運行需要怎么辦? 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對象執(zhí)行完畢后我再執(zhí)行"); } static public class MyThread extends Thread { @Override public void run() { System.out.println("我想先執(zhí)行"); } } }
上面的代碼僅僅加上了一句:threadTest.join();。在這里join方法的作用就是主線程需要等待子線程執(zhí)行完成之后再結束。
2.2 join(long millis)方法的使用join(long millis)中的參數(shù)就是設定的等待時間。
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) 和區(qū)別在于: Thread.sleep(2000)不會釋放鎖,threadTest.join(2000)會釋放鎖 。
三 ThreadLocal的使用變量值的共享可以使用public static變量的形式,所有線程都使用一個public static變量。如果想實現(xiàn)每一個線程都有自己的共享變量該如何解決呢?JDK中提供的ThreadLocal類正是為了解決這樣的問題。ThreadLocal類主要解決的就是讓每個線程綁定自己的值,可以將ThreadLocal類形象的比喻成存放數(shù)據(jù)的盒子,盒子中可以存儲每個線程的私有數(shù)據(jù)。
再舉個簡單的例子:
比如有兩個人去寶屋收集寶物,這兩個共用一個袋子的話肯定會產(chǎn)生爭執(zhí),但是給他們兩個人每個人分配一個袋子的話就不會出現(xiàn)這樣的問題。如果把這兩個人比作線程的話,那么ThreadLocal就是用來這兩個線程競爭的。
ThreadLocal類相關方法:
| 方法名稱 | 描述 |
| :-------- | --------:|
| get() | 返回當前線程的此線程局部變量的副本中的值。 |
| set(T value) | 將當前線程的此線程局部變量的副本設置為指定的值 |
| remove() | 刪除此線程局部變量的當前線程的值。|
| initialValue() | 返回此線程局部變量的當前線程的“初始值” |
Test1.java
public class Test1 { public static ThreadLocalt1 = new ThreadLocal (); 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對象的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(); } } } }
從運行結果可以看出子線程和父線程各自擁有各自的值。
運行結果:
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并發(fā)編程的藝術》
如果你覺得博主的文章不錯,歡迎轉發(fā)點贊。你能從中學到知識就是我最大的幸運。
歡迎關注我的微信公眾號:“Java面試通關手冊”(分享各種Java學習資源,面試題,以及企業(yè)級Java實戰(zhàn)項目回復關鍵字免費領取)。另外我創(chuàng)建了一個Java學習交流群(群號:174594747),歡迎大家加入一起學習,這里更有面試,學習視頻等資源的分享。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69099.html
摘要:相比與其他操作系統(tǒng)包括其他類系統(tǒng)有很多的優(yōu)點,其中有一項就是,其上下文切換和模式切換的時間消耗非常少。因為多線程競爭鎖時會引起上下文切換。減少線程的使用。很多編程語言中都有協(xié)程。所以如何避免死鎖的產(chǎn)生,在我們使用并發(fā)編程時至關重要。 系列文章傳送門: Java多線程學習(一)Java多線程入門 Java多線程學習(二)synchronized關鍵字(1) java多線程學習(二)syn...
摘要:因為多線程競爭鎖時會引起上下文切換。減少線程的使用。舉個例子如果說服務器的帶寬只有,某個資源的下載速度是,系統(tǒng)啟動個線程下載該資源并不會導致下載速度編程,所以在并發(fā)編程時,需要考慮這些資源的限制。 最近私下做一項目,一bug幾日未解決,總惶恐。一日頓悟,bug不可怕,怕的是項目不存在bug,與其懼怕,何不與其剛正面。 系列文章傳送門: Java多線程學習(一)Java多線程入門 Jav...
摘要:關鍵字加到非靜態(tài)方法上持有的是對象鎖。線程和線程持有的鎖不一樣,所以和運行同步,但是和運行不同步。所以盡量不要使用而使用參考多線程編程核心技術并發(fā)編程的藝術如果你覺得博主的文章不錯,歡迎轉發(fā)點贊。 系列文章傳送門: Java多線程學習(一)Java多線程入門 Java多線程學習(二)synchronized關鍵字(1) java多線程學習(二)synchronized關鍵字(2) J...
摘要:運行可運行狀態(tài)的線程獲得了時間片,執(zhí)行程序代碼。阻塞的情況分三種一等待阻塞運行的線程執(zhí)行方法,會把該線程放入等待隊列中。死亡線程方法執(zhí)行結束,或者因異常退出了方法,則該線程結束生命周期。死亡的線程不可再次復生。 系列文章傳送門: Java多線程學習(一)Java多線程入門 Java多線程學習(二)synchronized關鍵字(1) java多線程學習(二)synchronized關鍵...
摘要:轉載請備注地址多線程學習二將分為兩篇文章介紹同步方法另一篇介紹同步語句塊。如果兩個線程同時操作對象中的實例變量,則會出現(xiàn)非線程安全,解決辦法就是在方法前加上關鍵字即可。 轉載請備注地址: https://blog.csdn.net/qq_3433... Java多線程學習(二)將分為兩篇文章介紹synchronized同步方法另一篇介紹synchronized同步語句塊。系列文章傳送門...
閱讀 695·2021-11-15 11:37
閱讀 3316·2021-10-27 14:14
閱讀 6038·2021-09-13 10:30
閱讀 2961·2021-09-04 16:48
閱讀 1926·2021-08-18 10:22
閱讀 2125·2019-08-30 14:19
閱讀 728·2019-08-30 10:54
閱讀 1744·2019-08-29 18:40