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