摘要:它將管理線程的創(chuàng)建銷(xiāo)毀和復(fù)用,盡最大可能提高線程的使用效率。如果我們?cè)诹硪粋€(gè)線程中需要使用這個(gè)結(jié)果,則這個(gè)線程會(huì)掛起直到另一個(gè)線程返回該結(jié)果。我們無(wú)需再在另一個(gè)線程中使用回調(diào)函數(shù)來(lái)處理結(jié)果。
前言
Java的多線程機(jī)制允許我們將可以并行的任務(wù)分配給不同的線程同時(shí)完成。但是,如果我們希望在另一個(gè)線程的結(jié)果之上進(jìn)行后續(xù)操作,我們應(yīng)該怎么辦呢?
注:本文的代碼沒(méi)有經(jīng)過(guò)具體實(shí)踐的檢驗(yàn),純屬為了展示。如果有任何問(wèn)題,歡迎指出。
在此之前你需要了解Thread類(lèi)
Runnable接口
ExecutorServer, Executors生成的線程池
一個(gè)簡(jiǎn)單的場(chǎng)景假設(shè)我們現(xiàn)在有一個(gè)IO操作需要讀取一個(gè)文件,在讀取完成之后我們希望針對(duì)讀取的字節(jié)進(jìn)行相應(yīng)的處理。因?yàn)镮O操作比較耗時(shí),所以我們可能會(huì)希望在另一個(gè)線程中進(jìn)行IO操作,從而確保主線程的運(yùn)行不會(huì)出現(xiàn)等待。在這里,我們讀取完文件之后會(huì)在其所在線程輸出其字符流對(duì)應(yīng)的字符串。
//主線程類(lèi) public class MainThread { public static void main(String[] args){ performIO(); } public static void performIO(){ FileReader fileReader = new FileReader(FILENAME); Thread thread = new Thread(fileReader); thread.start(); } }
文件讀取類(lèi):
public class FileReader implements Runnable{ private FileInputStream fileInputStream; private String fileName; private byte[] content; public FileReader(String fileName){ this.fileName = fileName; content = new byte[2048]; } @Override public void run() { try { File file = new File(fileName); fileInputStream = new FileInputStream(file); int bytesRead = 0; while(fileInputStream.available() > 0){ bytesRead += fileInputStream.read(content, bytesRead, content.length - bytesRead); } System.out.println(new String(content,0, bytesRead)); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }一個(gè)錯(cuò)誤的例子
假設(shè)現(xiàn)在主線程希望針對(duì)文件的信息進(jìn)行操作,那么可能會(huì)出現(xiàn)以下的代碼:
在子線程中添加get方法返回讀取的字符數(shù)組:
public class FileReader implements Runnable{ private FileInputStream fileInputStream; private String fileName; private byte[] content; //添加get方法返回字符數(shù)組 public byte[] getContent(){ return this.content; } public FileReader(String fileName){ this.fileName = fileName; content = new byte[2048]; } @Override public void run() { try { File file = new File(fileName); fileInputStream = new FileInputStream(file); int bytesRead = 0; while(fileInputStream.available() > 0){ bytesRead += fileInputStream.read(content, bytesRead, content.length - bytesRead); } System.out.println(new String(content,0, bytesRead)); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
主線程方法中添加讀取byte數(shù)組的方法:
public class MainThread { public static void main(String[] args){ performIO(); } public static void performIO(){ FileReader fileReader = new FileReader(FILENAME); Thread thread = new Thread(fileReader); thread.start(); //讀取內(nèi)容 byte[] content = fileReader.getContent(); System.out.println(content); } }
這段代碼不能保證正常運(yùn)行,原因在于我們無(wú)法控制線程的調(diào)度。也就是說(shuō),在thread.start()語(yǔ)句后,主線程可能依然占有CPU繼續(xù)執(zhí)行,而此時(shí)獲得的content則是null。
你搞定了沒(méi)有啊主線程可以通過(guò)輪詢(xún)的方式詢(xún)問(wèn)IO線程是不是已經(jīng)完成了操作,如果完成了操作,就讀取結(jié)果。這里我們需要設(shè)置一個(gè)標(biāo)記位來(lái)記錄IO是否完成。
public class FileReader implements Runnable{ private FileInputStream fileInputStream; private String fileName; private byte[] content; //新建標(biāo)記位,初始為false public boolean finish; public byte[] getContent(){ return this. content; } public FileReader(String fileName){ this.fileName = fileName; content = new byte[2048]; } @Override public void run() { try { File file = new File(fileName); fileInputStream = new FileInputStream(file); int bytesRead = 0; while(fileInputStream.available() > 0){ bytesRead += fileInputStream.read(content, bytesRead, content.length - bytesRead); } finish = true; } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
主線程一直輪詢(xún)IO線程:
public class MainThread { public static void main(String[] args){ performIO(); } public static void performIO(){ FileReader fileReader = new FileReader(FILENAME); Thread thread = new Thread(fileReader); thread.start(); while(true){ if(fileReader.finish){ System.out.println(new String(fileReader.getContent())); break; } } } }
缺點(diǎn)那是相當(dāng)?shù)拿黠@,不斷的輪詢(xún)會(huì)無(wú)謂的消耗CPU。除此以外,一旦IO異常,則標(biāo)記位永遠(yuǎn)為false,主線程會(huì)陷入死循環(huán)。
搞定了告訴我一聲啊要解決這個(gè)問(wèn)題,我們就需要在IO線程完成讀取之后,通知主線程該操作已經(jīng)完成,從而主線程繼續(xù)運(yùn)行。這種方法叫做回調(diào)。可以用靜態(tài)方法實(shí)現(xiàn):
public class FileReader implements Runnable{ private FileInputStream fileInputStream; private String fileName; private byte[] content; public FileReader(String fileName){ this.fileName = fileName; content = new byte[2048]; } @Override public void run() { try { File file = new File(fileName); fileInputStream = new FileInputStream(file); int bytesRead = 0; while(fileInputStream.available() > 0){ bytesRead += fileInputStream.read(content, bytesRead, content.length - bytesRead); } //完成IO后調(diào)用主線程的回調(diào)函數(shù)來(lái)通知主線程進(jìn)行后續(xù)的操作 MainThread.callback(content); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
主線程方法中定義回調(diào)函數(shù):
public class MainThread { public static void main(String[] args){ performIO(); } //在主線程中用靜態(tài)方法定義回調(diào)函數(shù) public static void callback(byte[] content){ //do something System.out.println(content); } public static void performIO(){ FileReader fileReader = new FileReader(FILENAME); Thread thread = new Thread(fileReader); thread.start(); } }
這種實(shí)現(xiàn)方法的缺點(diǎn)在于MainThread和FileReader類(lèi)之間的耦合太強(qiáng)了。而且萬(wàn)一我們需要讀取多個(gè)文件,我們會(huì)希望對(duì)每一個(gè)FileReader有自己的callback函數(shù)進(jìn)行處理。因此我們可以callback將其聲明為一般函數(shù),并且讓IO線程持有需要回調(diào)的方法所在的實(shí)例:
public class FileReader implements Runnable{ private FileInputStream fileInputStream; private String fileName; private byte[] content; //持有回調(diào)函數(shù)的實(shí)例 private MainThread mainThread; //傳入實(shí)例 public FileReader(String fileName, MainThreand mainThread){ this.fileName = fileName; content = new byte[2048]; this.mainThread = mainThread; } @Override public void run() { try { File file = new File(fileName); fileInputStream = new FileInputStream(file); int bytesRead = 0; while(fileInputStream.available() > 0){ bytesRead += fileInputStream.read(content, bytesRead, content.length - bytesRead); } System.out.println(new String(content,0, bytesRead)); mainThread.callback(content); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
主線程方法中添加讀取byte數(shù)組的方法:
public class MainThread { public static void main(String[] args){ new MainThread().performIO(); } public void callback(byte[] content){ //do something } //將執(zhí)行IO變?yōu)榉庆o態(tài)方法 public void performIO(){ FileReader fileReader = new FileReader(FILENAME); Thread thread = new Thread(fileReader); thread.start(); } }搞定了告訴我們一聲啊
有時(shí)候可能有多個(gè)事件都在監(jiān)聽(tīng)事件,比如當(dāng)我點(diǎn)擊了Button,我希望后臺(tái)能夠執(zhí)行查詢(xún)操作并將結(jié)果返回給UI。同時(shí),我還希望將用戶(hù)的這個(gè)操作無(wú)論成功與否寫(xiě)入日志線程。因此,我可以寫(xiě)兩個(gè)回調(diào)函數(shù),分別對(duì)應(yīng)于不同的操作。
public interface Callback{ public void perform(T t); }
寫(xiě)入日志操作:
public class Log implements Callback{ public void perform(String s){ //寫(xiě)入日志 } }
IO讀取操作
public class FileReader implements Callback{ public void perform(String s){ //進(jìn)行IO操作 } }
public class Button{ private ListJava7: 行了,別忙活了,朕知道了callables; public Button(){ callables = new ArrayList (); } public void addCallable(Callable c){ this.callables.add(c); } public void onClick(){ for(Callable c : callables){ c.perform(...); } } }
Java7提供了非常方便的封裝Future,Callables和Executors來(lái)實(shí)現(xiàn)之前的回調(diào)工作。
之前我們直接將任務(wù)交給一個(gè)新建的線程來(lái)處理。可是如果每次都新建一個(gè)線程來(lái)處理當(dāng)前的任務(wù),線程的新建和銷(xiāo)毀將會(huì)是一大筆開(kāi)銷(xiāo)。因此Java提供了多種類(lèi)型的線程池來(lái)供我們操作。它將管理線程的創(chuàng)建銷(xiāo)毀和復(fù)用,盡最大可能提高線程的使用效率。
同時(shí)Java7提供的Callable接口將自動(dòng)返回線程運(yùn)行結(jié)束的結(jié)果。如果我們?cè)诹硪粋€(gè)線程中需要使用這個(gè)結(jié)果,則這個(gè)線程會(huì)掛起直到另一個(gè)線程返回該結(jié)果。我們無(wú)需再在另一個(gè)線程中使用回調(diào)函數(shù)來(lái)處理結(jié)果。
假設(shè)現(xiàn)在我們想要找到一個(gè)數(shù)組的最大值。假設(shè)該數(shù)組容量驚人,因此我們希望新開(kāi)兩個(gè)線程分別對(duì)數(shù)組的前半部分和后半部分計(jì)算最大值。然后在主線程中比較兩個(gè)結(jié)果得出結(jié)論:
public class ArrayMaxValue { public static void main(String[] args){ Random r = new Random(20); int[] array = new int[500]; for (int i = 0 ; if1 = executorService.submit(new MaxValue(array, 0, mid)); Future f2 = executorService.submit(new MaxValue(array, mid, array.length)); try { //主線程將阻塞自己直到兩個(gè)線程都完成運(yùn)行,并返回結(jié)果 System.out.println(Math.max(f1.get(), f2.get())); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } public class MaxValue implements Callable { private final int[] array; private final int startIndex; private final int endIndex; public MaxValue(int[] array, int startIndex, int endIndex){ this.array = array; this.startIndex = startIndex; this.endIndex = endIndex; } @Override public Integer call() throws Exception { int max = Integer.MIN_VALUE; for (int i = startIndex ; i 參考文章 深入理解線程通信
想要了解更多開(kāi)發(fā)技術(shù),面試教程以及互聯(lián)網(wǎng)公司內(nèi)推,歡迎關(guān)注我的微信公眾號(hào)!將會(huì)不定期的發(fā)放福利哦~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/76369.html
摘要:阿里開(kāi)始招實(shí)習(xí),同學(xué)問(wèn)我要不要去申請(qǐng)阿里的實(shí)習(xí),我說(shuō)不去,個(gè)人對(duì)阿里的印象不好。記得去年阿里給我發(fā)了郵件,我很認(rèn)真地回復(fù),然后他不理我了。 引言 最近好久沒(méi)有遇到技術(shù)瓶頸了,思考得自然少了,每天都是重復(fù)性的工作。 阿里開(kāi)始招實(shí)習(xí),同學(xué)問(wèn)我要不要去申請(qǐng)阿里的實(shí)習(xí),我說(shuō)不去,個(gè)人對(duì)阿里的印象不好。 記得去年阿里給我發(fā)了郵件,我很認(rèn)真地回復(fù),然后他不理我了。(最起碼的尊重都沒(méi)有,就算我菜你起...
摘要:前言今天,我將梳理在網(wǎng)絡(luò)編程中很重要的一個(gè)類(lèi)以及其相關(guān)的類(lèi)。這類(lèi)主機(jī)通常不需要外部互聯(lián)網(wǎng)服務(wù),僅有主機(jī)間相互通訊的需求。可以通過(guò)該接口獲取所有本地地址,并根據(jù)這些地址創(chuàng)建。在這里我們使用阻塞隊(duì)列實(shí)現(xiàn)主線程和打印線程之間的通信。 前言 今天,我將梳理在Java網(wǎng)絡(luò)編程中很重要的一個(gè)類(lèi)InetAddress以及其相關(guān)的類(lèi)NetworkInterface。在這篇文章中將會(huì)涉及: InetA...
摘要:從而一方面減少了響應(yīng)時(shí)間,另一方面減少了服務(wù)器的壓力。表明響應(yīng)只能被單個(gè)用戶(hù)緩存,不能作為共享緩存即代理服務(wù)器不能緩存它。這種情況稱(chēng)為服務(wù)器再驗(yàn)證。否則會(huì)返回響應(yīng)。 前言 本文將根據(jù)最近所學(xué)的Java網(wǎng)絡(luò)編程實(shí)現(xiàn)一個(gè)簡(jiǎn)單的基于URL的緩存。本文將涉及如下內(nèi)容: HTTP協(xié)議 HTTP協(xié)議中與緩存相關(guān)的內(nèi)容 URLConnection 和 HTTPURLConnection Respo...
摘要:從而一方面減少了響應(yīng)時(shí)間,另一方面減少了服務(wù)器的壓力。表明響應(yīng)只能被單個(gè)用戶(hù)緩存,不能作為共享緩存即代理服務(wù)器不能緩存它。這種情況稱(chēng)為服務(wù)器再驗(yàn)證。否則會(huì)返回響應(yīng)。 前言 本文將根據(jù)最近所學(xué)的Java網(wǎng)絡(luò)編程實(shí)現(xiàn)一個(gè)簡(jiǎn)單的基于URL的緩存。本文將涉及如下內(nèi)容: HTTP協(xié)議 HTTP協(xié)議中與緩存相關(guān)的內(nèi)容 URLConnection 和 HTTPURLConnection Respo...
摘要:探究系統(tǒng)登錄驗(yàn)證碼的實(shí)現(xiàn)后端掘金驗(yàn)證碼生成類(lèi)手把手教程后端博客系統(tǒng)第一章掘金轉(zhuǎn)眼間時(shí)間就從月份到現(xiàn)在的十一月份了。提供了與標(biāo)準(zhǔn)不同的工作方式我的后端書(shū)架后端掘金我的后端書(shū)架月前本書(shū)架主要針對(duì)后端開(kāi)發(fā)與架構(gòu)。 Spring Boot干貨系列總綱 | 掘金技術(shù)征文 - 掘金原本地址:Spring Boot干貨系列總綱博客地址:http://tengj.top/ 前言 博主16年認(rèn)識(shí)Spin...
閱讀 1619·2021-11-11 10:59
閱讀 2624·2021-09-04 16:40
閱讀 3650·2021-09-04 16:40
閱讀 2979·2021-07-30 15:30
閱讀 1615·2021-07-26 22:03
閱讀 3164·2019-08-30 13:20
閱讀 2225·2019-08-29 18:31
閱讀 439·2019-08-29 12:21