IO與NIO –中斷,超時和緩沖區

假設有一個系統有時需要將文件復制到幾個位置,但是這種方式在響應速度至關重要的情況下。 換句話說,如果由于某種原因文件系統過載,并且我們無法在不到一秒鐘的時間內寫入文件,則應該放棄。

ExecutorService是一項非常方便的工作工具。 您可以輕松地將其用于并行執行多個任務(每個任務都寫入不同的文件系統)。 Yuo還可以告訴它在超時后放棄,它將為您打斷他們。 完美,正是我們所需要的。

腳手架看起來像這樣:

void testCopy() throws Exception {ThreadPoolExecutor exec = (ThreadPoolExecutor) Executors.newCachedThreadPool();final long start = System.currentTimeMillis();Callable<Object> task = new Callable<Object>() {@Overridepublic Object call() throws Exception {try {copy("a.bin", "b.bin");} catch (Exception e) {e.printStackTrace();}System.out.println("Call really finished after: "+ (System.currentTimeMillis() - start));return null;}};Collection<Callable<Object>> taskWrapper = Arrays.asList(task);List<Future<Object>> futures = exec.invokeAll(taskWrapper, 50,TimeUnit.MILLISECONDS);System.out.println("invokeAll finished after: "+ (System.currentTimeMillis() - start));System.out.println("Future.isCancelled? "+ futures.get(0).isCancelled());Thread.sleep(20);System.out.println("Threads still active: " + exec.getActiveCount());
}

為了在低負載的運行狀況良好的系統上模擬對超時的響應,我使用了100 MB的文件并且超時非常短。 任務總是超時,我的系統無法在50毫秒內復制100 MB。

我期望得到以下結果:

  1. 大約50毫秒后, invokeAll完成。
  2. Future.isCancelled? 是真的。
  3. 活動線程計數為0。通過睡眠可以消除某些邊緣情況。 長話短說,它給了復制功能一些時間來檢測中斷。
  4. 通話大約在50毫秒后真正結束。 這非常重要,我絕對不希望取消任務后繼續執行IO操作。 在較高的負載下,這會導致過多的線程卡在虛假的IO中。

以防萬一,這些測試是在64位Windows 7上的Oracle 1.6 JVM上運行的。

解決方案1:流復制

第一次嘗試可能很簡單-使用帶有緩沖區和經典IO的循環,如下所示:

private void copy(String in, String out) throws Exception {FileInputStream fin = new FileInputStream(in);FileOutputStream fout = new FileOutputStream(out);byte[] buf = new byte[4096];int read;while ((read = fin.read(buf)) > -1) {fout.write(buf, 0, read);}fin.close();fout.close();
}

這就是所有流行的流復制庫做的,包括IOUtils Apache的共享和ByteStreams番石榴。

它也不幸地失敗了:

invokeAll finished after: 53
Future.isCancelled? true
Threads still active: 1
Call really finished after: 338

原因很明顯:在循環中或任何地方都不檢查線程中斷狀態,因此線程可以正常繼續。

解決方案2:通過復制檢查流是否中斷

讓我們解決這個問題! 一種方法是:

while ((read = fin.read(buf)) > -1) {fout.write(buf, 0, read);if (Thread.interrupted()) {throw new IOException("Thread interrupted, cancelling");}
}

現在可以正常工作了,打印:

invokeAll finished after: 52
java.io.IOException: Thread interrupted, cancellingat TransferTest.copyInterruptingStream(TransferTest.java:75)at TransferTest.access$0(TransferTest.java:66)at TransferTest$1.call(TransferTest.java:25)at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)at java.util.concurrent.FutureTask.run(FutureTask.java:138)at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)Future.isCancelled? trueat java.lang.Thread.run(Thread.java:662)Call really finished after: 53
Threads still active: 0

很好,但是我覺得不滿意。 它看起來很臟,我對自己的IO庫中的這段代碼并不特別滿意。 必須有更好的方法,這將我們帶到……

解決方案3:帶傳輸的NIO

NIO具有這個不錯的功能,它實際上尊重線程中斷。 如果在線程中斷后嘗試讀取或寫入通道,則會收到ClosedByInterruptException

那正是我所需要的。 由于某種原因,我還在StackOverflow上閱讀了以下答案 :

“如果不需要,請不要使用緩沖區。 如果目標是其他磁盤或NIC,為什么還要復制到內存中? 對于較大的文件,確保的延遲是不平凡的。 (…)使用FileChannel.transferTo()FileChannel.transferFrom() 。 此處的主要優勢在于,JVM使用操作系統對DMA(直接內存訪問)的訪問(如果存在)。 (這取決于實現方式,但是可以在通用CPU上使用現代的Sun和IBM版本。)發生的情況是,數據直接通過/從磁盤,到總線,再到目的地……直接通過RAM傳遞任何電路或CPU。”

太好了,讓我們做吧!

private void copy(String in, String out) throws Exception {FileChannel fin = new FileInputStream(in).getChannel();FileChannel fout = new FileOutputStream(out).getChannel();fout.transferFrom(fin, 0, new File(in).length());fin.close();fout.close();
}

輸出:

invokeAll finished after: 52
Future.isCancelled? true
Threads still active: 1
java.nio.channels.ClosedByInterruptExceptionat java.nio.channels.spi.AbstractInterruptibleChannel.end(AbstractInterruptibleChannel.java:184)at sun.nio.ch.FileChannelImpl.size(FileChannelImpl.java:304)at sun.nio.ch.FileChannelImpl.transferFrom(FileChannelImpl.java:587)at TransferTest.copyNioTransfer(TransferTest.java:91)at TransferTest.access$0(TransferTest.java:87)at TransferTest$1.call(TransferTest.java:27)at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)at java.util.concurrent.FutureTask.run(FutureTask.java:138)at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)at java.lang.Thread.run(Thread.java:662)
Call really finished after: 146

我所要做的只是簡單地調用transferFrom 。 非常簡潔,并承諾會從硬件和操作系統中獲得如此多的支持……但是請稍等一下,為什么要花146毫秒? 我的意思是,146毫秒比第一次測試中的338毫秒快得多,但是我希望它在50毫秒后終止。

讓我們在更大的文件(大約1.5 GB)上重復測試:

invokeAll finished after: 9012
Future.isCancelled? true
Threads still active: 1
java.nio.channels.ClosedByInterruptExceptionat java.nio.channels.spi.AbstractInterruptibleChannel.end(AbstractInterruptibleChannel.java:184)(...)
Call really finished after: 9170

那有多可怕? 這可能是可能發生的最糟糕的事情:

  • 任務未及時中斷。 9秒太長了,我預計大約50毫秒。
  • 在整個操作過程中(9秒), invokeAll被阻止。 我勒個去?

解決方案4 –帶緩沖的NIO

事實證明,我確實需要一些緩沖。 讓我們嘗試一下:

private void copyNioBuffered(String in, String out) throws Exception {FileChannel fin = new FileInputStream(in).getChannel();FileChannel fout = new FileOutputStream(out).getChannel();ByteBuffer buff = ByteBuffer.allocate(4096);while (fin.read(buff) != -1 || buff.position() > 0) {buff.flip();fout.write(buff);buff.compact();}fin.close();fout.close();
}

輸出:

invokeAll finished after: 52
Future.isCancelled? true
java.nio.channels.ClosedByInterruptExceptionat java.nio.channels.spi.AbstractInterruptibleChannel.end(AbstractInterruptibleChannel.java:184)at sun.nio.ch.FileChannelImpl.write(FileChannelImpl.java:203)at TransferTest.copyNioBuffered(TransferTest.java:105)at TransferTest.access$0(TransferTest.java:98)at TransferTest$1.call(TransferTest.java:29)at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)at java.util.concurrent.FutureTask.run(FutureTask.java:138)at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)at java.lang.Thread.run(Thread.java:662)
Call really finished after: 55
Threads still active: 0

現在正是我所需要的。 它本身就考慮到中斷,因此我不需要整個IO實用程序進行那些繁瑣的檢查。 怪癖:不同類型的頻道

如果我的IO實用程序僅用于復制按名稱獲取的文件,如下所示:

static public void copy(String source, String destination)

…然后很容易為NIO重寫方法。

但是,如果它是在流上運行的更通用的簽名,該怎么辦?

static public void copy(InputStream source, OutputStream destination)

NIO有一個Channels實用程序,它具有非常有用的方法,例如:

public static ReadableByteChannel newChannel(InputStream in)
public static WritableByteChannel newChannel(OutputStream out)

因此,似乎我們可以使用此幫助程序包裝流并從可中斷的NIO API中受益。 在我們查看源代碼之前:

public static WritableByteChannel newChannel(final OutputStream out) {if (out == null) {throw new NullPointerException();}if (out instanceof FileOutputStream &&FileOutputStream.class.equals(out.getClass())) {return ((FileOutputStream)out).getChannel();}return new WritableByteChannelImpl(out);
}private static class WritableByteChannelImplextends AbstractInterruptibleChannel // Not really interruptibleimplements WritableByteChannel
{
// ... Ignores interrupts completely

小心! 如果您的流是文件流,它們將是可中斷的。 否則,您很不走運–它只是一個愚蠢的包裝器,更像是API兼容性的適配器。 假設殺死,總是檢查源頭。

參考: IO與NIO – 松鼠博客上來自我們JCG合作伙伴 Konrad Garus的中斷,超時和緩沖區 。


翻譯自: https://www.javacodegeeks.com/2012/07/io-vs-nio-interruptions-timeouts-and.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/372159.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/372159.shtml
英文地址,請注明出處:http://en.pswp.cn/news/372159.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

實驗5 matlab程序設計2,實驗5 Matlab程序設計2

實驗5 Matlab程序設計21. 實驗目的&#xff1a;2. 掌握建立和執行M文件的方法&#xff1b; 3. 掌握實現選擇結構的方法&#xff1b; 4. 掌握實現循環結構的方法。5. 熟悉利用向量運算來代替循環操作的方法。 6. 實驗內容&#xff1a;27. 根據61111 122232n2&#xff0c;求π的近…

【poj1041】 John's trip

http://poj.org/problem?id1041 (題目鏈接) 題意 給出一張無向圖&#xff0c;求字典序最小歐拉回路。 Solution 這鬼畜的輸入是什么心態啊mdzz&#xff0c;這里用vector儲存邊&#xff0c;便于邊的排序。瞬間變成STL常數boy →_→。 細節 數組大小把握好。 代碼 // poj1041 #i…

記一次ora-1652錯誤的解決過程

報錯現象&#xff1a; 通過v$RMAN_BACKUP_JOB_DETAILS查看備份狀態&#xff0c;一直卡著不出結果&#xff0c;很長一段時間之后拋出ORA-1652: unable to extend temp segment by 128 in tablespace &#xff0c;此時查看臨時表空間使用情況&#xff0c;發現占用很少&#xff0c…

帶有docx4j的Java Word(.docx)文檔

幾個月前&#xff0c;我需要創建一個包含許多表和段落的動態Word文檔。 過去&#xff0c;我曾使用POI來實現此目的&#xff0c;但是我發現它很難使用&#xff0c;并且在創建更復雜的文檔時對我來說效果不佳。 因此&#xff0c;對于這個項目&#xff0c;經過一番搜索&#xff0c…

mysql中distinct關鍵字,MySQL關鍵字Distinct的詳細介紹

DDLPrepare SQL&#xff1a;?Prepare Data&#xff1a;?查詢數據如下圖所示&#xff1a;第一種情況&#xff0c;使用Distinct關鍵字&#xff0c;查詢單列數據&#xff0c;如下圖所示&#xff1a;結果&#xff1a;對 name 字段進行去重處理&#xff0c;符合預期期望&#xff0…

#pragma 預處理指令

Linux C 編程一站式學習 #pragma 預處理指示供編譯器實現一些非標準的特性&#xff0c;C 標準沒有規定 #pragma 后面應該寫什么以及起什么作用&#xff0c;由編譯器自己規定。有的編譯器用 #pragma 定義一些特殊功能寄存器名&#xff0c;有的編譯器用 #pragma 定位鏈接地址&…

px ,em ,rem

做移動端或者響應式的頁面必然需要字體的變化的。這次我就自己的經驗來說說他們之間的關系&#xff0c;以及怎么用。 px (絕對單位)是我們常用的就不說了。 em&#xff08;相對單位&#xff0c;相對父級&#xff09; em 指字體高&#xff0c;任意瀏覽器的默認字體高都是16px。所…

使用JAnnocessor生成Java代碼

在本文中&#xff0c;我將向你展示如何生成的代碼JAnnocessor通過創建框架Nikolche Mihajlovski 。 在Nikolche的演講中&#xff0c;我第一次在GeeCON 2012大會上遇到JAnnocessor&#xff1a; “創新和實用的Java源代碼生成” &#xff08;幻燈片&#xff09; 。 之后&#xff…

Linq學習筆記(轉)

開始Linq前你要知道的 擴展方法 顧名思義就是對現有類進行擴展的的方法&#xff0c;擴展方法可以在不修改現有類的情況下&#xff0c;為現有類增加公共的接口&#xff08;不是C#中的interface&#xff09;。 擴展方法本質上是一個靜態方法&#xff0c;不同之處在于它的第一個參…

cass展點不在原位置,cass中打開一副圖后,通過繪圖處理-——展高程點,怎么之前的圖形就不顯示了,,只剩下高程點!!...

答&#xff1a;1、進入控制面板&#xff0c;選擇“卸載或更改程序”。 2、選中“AutoCAD2006”圖標。 3、右擊選擇“更改”。 4、進入“AutoCAD2006安裝程序對話框”&#xff0c;選擇“添加/刪除功能”單選按鈕&#xff0c;點擊下一步。 5、在“程序文件”列表中&#xff0c;選…

(二)windows下安裝PHPCMS V9

一、準備工作 搭建環境 &#xff1a;參考:Windows下搭建PHP開發環境及相關注意事項PHPCMS V9 &#xff1a;下載適合自己 PHPCMS V9 版本到本地或服務器&#xff0c;下載地址&#xff1a;http://www.phpcms.cn/html/download/說明&#xff1a;官方提供了 2 種不同的編碼。包括 G…

JavaFX 2.0布局窗格– HBox和VBox

如果要對JavaFX 2.0中所有不同的布局窗格進行概述&#xff0c;或者想了解有關它們的一些基本知識&#xff0c;請參閱我以前的文章《 JavaFX 2.0中的布局窗格》 。 布局窗格HBox和VBox絕對是JavaFX 2.0中最基本的布局容器。 如您所知&#xff0c;它們的用途是將所有子級布置在一…

flask mysql分頁,Flask分頁的實現方法

所需環境Flask-SQLAlchemy分頁使用Flask-SQLAlchemy提供的pagination()方法。頁數是pagination()方法的第一個參數&#xff0c;也是唯一必須的參數。可選參數per_page用來指定每頁顯示的記錄數。參考代碼&#xff1a;def index():# ...page request.args.get(page, 1, typeint…

Java中的生成器設計模式

Java 中的 Builder設計模式是一種創建模式&#xff0c;即用于創建對象&#xff0c;類似于 工廠方法設計模式 &#xff0c;這也是創建設計模式。 在學習任何設計模式之前&#xff0c;我建議先找出特定設計模式要解決的問題。 眾所周知&#xff0c; 必要性是發明的母親。 在沒有面…

驗證碼( 隨機數)

方式一&#xff08;變色版&#xff09;&#xff1a; <html> <head><meta charset"UTF-8"/><title></title><script src"jquery-2.0.2.min.js"></script> </head> <body> <?php header("co…

單片機串行通信全解析

1.什么是串行通信&#xff1f; 串行通信&#xff08;英語&#xff1a;Serial communication&#xff09;是指在計算機總線或其他數據通道上&#xff0c;每次傳輸一個位元數據&#xff0c;并連續進行以上單次過程的通信方式。與之對應的是并行通信&#xff0c;它在串行端口上通過…

java type 類型,java中的泛型類型與Type接口

假設我們定義了一個Room的類&#xff0c;表示一個房間public classRoom(){}由于我們建造好房間是&#xff0c;不知道房間以后的用途&#xff0c;他可能用來住人&#xff0c;也有可能用來放貨物&#xff0c;因此需要用到泛型。但是我們可能想獲取Room這個房間里面進來的的東西的…

centos7下操作防火墻

引言 最近使用centos7系統比較頻繁&#xff0c;在配置服務器的時候&#xff0c;總是遇到能夠ping通服務器&#xff0c;但是就是沒有辦法訪問80端口&#xff0c;這個時候我的直覺告訴我&#xff0c;肯定是防火墻的原因&#xff0c;但是使用iptables卻怎么都找不到命令&#xff0…

其他團隊對本團隊評價的總結

我們小組在看了其他小組的評價后&#xff0c;對自己的程序有了新的看法。轉載于:https://www.cnblogs.com/bk1246788/p/6879691.html

Java:使用Fork / Join框架的Mergesort

此項的目的是顯示一個Fork / Join RecursiveAction的簡單示例&#xff0c;而不是過多地研究合并合并的可能優化方法&#xff0c;或者比使用Exkutor / Join Pool優于現有的基于Java 6的現有實現&#xff08;例如ExecutorService&#xff09;的相對優勢。 以下是使用Java的自上而…