摘要:它將管理線程的創建銷毀和復用,盡最大可能提高線程的使用效率。如果我們在另一個線程中需要使用這個結果,則這個線程會掛起直到另一個線程返回該結果。我們無需再在另一個線程中使用回調函數來處理結果。
前言
Java的多線程機制允許我們將可以并行的任務分配給不同的線程同時完成。但是,如果我們希望在另一個線程的結果之上進行后續操作,我們應該怎么辦呢?
注:本文的代碼沒有經過具體實踐的檢驗,純屬為了展示。如果有任何問題,歡迎指出。
在此之前你需要了解Thread類
Runnable接口
ExecutorServer, Executors生成的線程池
一個簡單的場景假設我們現在有一個IO操作需要讀取一個文件,在讀取完成之后我們希望針對讀取的字節進行相應的處理。因為IO操作比較耗時,所以我們可能會希望在另一個線程中進行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(); } }
文件讀取類:
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(); } } }一個錯誤的例子
假設現在主線程希望針對文件的信息進行操作,那么可能會出現以下的代碼:
在子線程中添加get方法返回讀取的字符數組:
public class FileReader implements Runnable{ private FileInputStream fileInputStream; private String fileName; private byte[] content; //添加get方法返回字符數組 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數組的方法:
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(); //讀取內容 byte[] content = fileReader.getContent(); System.out.println(content); } }
這段代碼不能保證正常運行,原因在于我們無法控制線程的調度。也就是說,在thread.start()語句后,主線程可能依然占有CPU繼續執行,而此時獲得的content則是null。
你搞定了沒有啊主線程可以通過輪詢的方式詢問IO線程是不是已經完成了操作,如果完成了操作,就讀取結果。這里我們需要設置一個標記位來記錄IO是否完成。
public class FileReader implements Runnable{ private FileInputStream fileInputStream; private String fileName; private byte[] content; //新建標記位,初始為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(); } } }
主線程一直輪詢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; } } } }
缺點那是相當的明顯,不斷的輪詢會無謂的消耗CPU。除此以外,一旦IO異常,則標記位永遠為false,主線程會陷入死循環。
搞定了告訴我一聲啊要解決這個問題,我們就需要在IO線程完成讀取之后,通知主線程該操作已經完成,從而主線程繼續運行。這種方法叫做回調??梢杂渺o態方法實現:
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后調用主線程的回調函數來通知主線程進行后續的操作 MainThread.callback(content); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
主線程方法中定義回調函數:
public class MainThread { public static void main(String[] args){ performIO(); } //在主線程中用靜態方法定義回調函數 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(); } }
這種實現方法的缺點在于MainThread和FileReader類之間的耦合太強了。而且萬一我們需要讀取多個文件,我們會希望對每一個FileReader有自己的callback函數進行處理。因此我們可以callback將其聲明為一般函數,并且讓IO線程持有需要回調的方法所在的實例:
public class FileReader implements Runnable{ private FileInputStream fileInputStream; private String fileName; private byte[] content; //持有回調函數的實例 private MainThread mainThread; //傳入實例 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數組的方法:
public class MainThread { public static void main(String[] args){ new MainThread().performIO(); } public void callback(byte[] content){ //do something } //將執行IO變為非靜態方法 public void performIO(){ FileReader fileReader = new FileReader(FILENAME); Thread thread = new Thread(fileReader); thread.start(); } }搞定了告訴我們一聲啊
有時候可能有多個事件都在監聽事件,比如當我點擊了Button,我希望后臺能夠執行查詢操作并將結果返回給UI。同時,我還希望將用戶的這個操作無論成功與否寫入日志線程。因此,我可以寫兩個回調函數,分別對應于不同的操作。
public interface Callback{ public void perform(T t); }
寫入日志操作:
public class Log implements Callback{ public void perform(String s){ //寫入日志 } }
IO讀取操作
public class FileReader implements Callback{ public void perform(String s){ //進行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來實現之前的回調工作。
之前我們直接將任務交給一個新建的線程來處理??墒侨绻看味夹陆ㄒ粋€線程來處理當前的任務,線程的新建和銷毀將會是一大筆開銷。因此Java提供了多種類型的線程池來供我們操作。它將管理線程的創建銷毀和復用,盡最大可能提高線程的使用效率。
同時Java7提供的Callable接口將自動返回線程運行結束的結果。如果我們在另一個線程中需要使用這個結果,則這個線程會掛起直到另一個線程返回該結果。我們無需再在另一個線程中使用回調函數來處理結果。
假設現在我們想要找到一個數組的最大值。假設該數組容量驚人,因此我們希望新開兩個線程分別對數組的前半部分和后半部分計算最大值。然后在主線程中比較兩個結果得出結論:
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 { //主線程將阻塞自己直到兩個線程都完成運行,并返回結果 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 參考文章 深入理解線程通信
想要了解更多開發技術,面試教程以及互聯網公司內推,歡迎關注我的微信公眾號!將會不定期的發放福利哦~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/76369.html
摘要:阿里開始招實習,同學問我要不要去申請阿里的實習,我說不去,個人對阿里的印象不好。記得去年阿里給我發了郵件,我很認真地回復,然后他不理我了。 引言 最近好久沒有遇到技術瓶頸了,思考得自然少了,每天都是重復性的工作。 阿里開始招實習,同學問我要不要去申請阿里的實習,我說不去,個人對阿里的印象不好。 記得去年阿里給我發了郵件,我很認真地回復,然后他不理我了。(最起碼的尊重都沒有,就算我菜你起...
摘要:前言今天,我將梳理在網絡編程中很重要的一個類以及其相關的類。這類主機通常不需要外部互聯網服務,僅有主機間相互通訊的需求??梢酝ㄟ^該接口獲取所有本地地址,并根據這些地址創建。在這里我們使用阻塞隊列實現主線程和打印線程之間的通信。 前言 今天,我將梳理在Java網絡編程中很重要的一個類InetAddress以及其相關的類NetworkInterface。在這篇文章中將會涉及: InetA...
摘要:從而一方面減少了響應時間,另一方面減少了服務器的壓力。表明響應只能被單個用戶緩存,不能作為共享緩存即代理服務器不能緩存它。這種情況稱為服務器再驗證。否則會返回響應。 前言 本文將根據最近所學的Java網絡編程實現一個簡單的基于URL的緩存。本文將涉及如下內容: HTTP協議 HTTP協議中與緩存相關的內容 URLConnection 和 HTTPURLConnection Respo...
摘要:從而一方面減少了響應時間,另一方面減少了服務器的壓力。表明響應只能被單個用戶緩存,不能作為共享緩存即代理服務器不能緩存它。這種情況稱為服務器再驗證。否則會返回響應。 前言 本文將根據最近所學的Java網絡編程實現一個簡單的基于URL的緩存。本文將涉及如下內容: HTTP協議 HTTP協議中與緩存相關的內容 URLConnection 和 HTTPURLConnection Respo...
摘要:探究系統登錄驗證碼的實現后端掘金驗證碼生成類手把手教程后端博客系統第一章掘金轉眼間時間就從月份到現在的十一月份了。提供了與標準不同的工作方式我的后端書架后端掘金我的后端書架月前本書架主要針對后端開發與架構。 Spring Boot干貨系列總綱 | 掘金技術征文 - 掘金原本地址:Spring Boot干貨系列總綱博客地址:http://tengj.top/ 前言 博主16年認識Spin...
閱讀 1627·2021-11-11 10:59
閱讀 2635·2021-09-04 16:40
閱讀 3672·2021-09-04 16:40
閱讀 2990·2021-07-30 15:30
閱讀 1670·2021-07-26 22:03
閱讀 3172·2019-08-30 13:20
閱讀 2236·2019-08-29 18:31
閱讀 447·2019-08-29 12:21