Java 7#8:测试台上的NIO.2文件通道
由于這些文件通道是異步工作的,因此與常規(guī)I / O相比它們的性能很有意思。 第二部分處理諸如內(nèi)存和CPU消耗之類的問(wèn)題,并說(shuō)明如何在高性能方案中安全地使用新的NIO.2通道。 您還需要了解如何在不丟失數(shù)據(jù)的情況下關(guān)閉異步通道,這是第三部分。 最后,在第四部分中,我們將研究并發(fā)性。
注意:我不會(huì)解釋異步文件通道的完整API。 那里有足夠的帖子在這方面做得很好。 我的帖子更深入地介紹了實(shí)用性和使用異步文件通道時(shí)可能遇到的問(wèn)題。
好吧,足夠模糊的談話,讓我們開始吧。 這是一個(gè)代碼片段,它打開一個(gè)異步通道(第7行),將字節(jié)序列寫入文件的開頭(第9行),并等待結(jié)果返回(第10行)。 最后,在第14行中關(guān)閉通道。
public class CallGraph_Default_AsynchronousFileChannel {private static AsynchronousFileChannel fileChannel;public static void main(String[] args) throws InterruptedException, IOException, ExecutionException {try {fileChannel = AsynchronousFileChannel.open(Paths.get("E:/temp/afile.out"), StandardOpenOption.READ,StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.DELETE_ON_CLOSE);Future<Integer> future = fileChannel.write(ByteBuffer.wrap("Hello".getBytes()), 0L);future.get();} catch (Exception e) {e.printStackTrace();} finally {fileChannel.close();}} }異步文件通道調(diào)用的重要參與者
在繼續(xù)研究代碼之前,讓我們快速介紹一下異步(文件)通道星系中涉及的概念。 圖1中的調(diào)用圖顯示了對(duì)AsynchronousFileChannel類的open()方法的調(diào)用中的序列圖。 FileSystemProvider封裝所有操作系統(tǒng)詳細(xì)信息。 為了逗大家,我在編寫本文時(shí)正在使用Windows XP客戶端。 因此,WindowsFileSystemProvider調(diào)用實(shí)際創(chuàng)建文件的WindowsChannelFactory并調(diào)用WindowsAsynchronousFileChannelImpl,后者返回其自身的實(shí)例。 最重要的概念是Iocp,即I / O完成端口。 它是用于執(zhí)行多個(gè)同時(shí)異步輸入/輸出操作的API。 創(chuàng)建完成端口對(duì)象,并將其與許多文件句柄關(guān)聯(lián)。 當(dāng)在對(duì)象上請(qǐng)求I / O服務(wù)時(shí),將通過(guò)排隊(duì)到I / O完成端口的消息來(lái)指示完成。 不向其他請(qǐng)求I / O服務(wù)的進(jìn)程通知I / O服務(wù)已完成,而是檢查I / O完成端口的消息隊(duì)列以確定其I / O請(qǐng)求的狀態(tài)。 I / O完成端口管理多個(gè)線程及其并發(fā)。 從圖中可以看出Iocp是AsynchronousChannelGroup的子類型。 因此,在JDK 7異步通道中,異步通道組被實(shí)現(xiàn)為I / O完成端口。 它擁有負(fù)責(zé)執(zhí)行所請(qǐng)求的異步I / O操作的ThreadPool。 ThreadPool實(shí)際上封裝了ThreadPoolExecutor,它執(zhí)行Java 1.5以來(lái)的所有多線程異步任務(wù)執(zhí)行管理。 對(duì)異步文件通道的寫操作將導(dǎo)致對(duì)ThreadPoolExecutor.execute()方法的調(diào)用。
一些基準(zhǔn)
查看性能總是很有趣。 異步非阻塞I / O必須快速,對(duì)嗎? 為了找到該問(wèn)題的答案,我進(jìn)行了基準(zhǔn)分析。 同樣,我使用亨氏微小的基準(zhǔn)框架來(lái)做到這一點(diǎn)。 我的機(jī)器是2.90 GHz的Intel Core i5-2310 CPU,具有四個(gè)內(nèi)核(64位)。 在基準(zhǔn)測(cè)試中,我需要一個(gè)基準(zhǔn)。 我的基線是對(duì)普通文件的簡(jiǎn)單常規(guī)同步寫入操作。 這是代碼段:
public class Performance_Benchmark_ConventionalFileAccessExample_1 implements Runnable {private static FileOutputStream outputfile;private static byte[] content = "Hello".getBytes();public static void main(String[] args) throws InterruptedException, IOException {try {System.out.println("Test: " + Performance_Benchmark_ConventionalFileAccessExample_1.class.getSimpleName());outputfile = new FileOutputStream(new File("E:/temp/afile.out"), true);Average average = new PerformanceHarness().calculatePerf(new PerformanceChecker(1000, new Performance_Benchmark_ConventionalFileAccessExample_1()), 5);System.out.println("Mean: " + DecimalFormat.getInstance().format(average.mean()));System.out.println("Std. Deviation: " + DecimalFormat.getInstance().format(average.stddev()));} catch (Exception e) {e.printStackTrace();} finally {new SystemInformation().printThreadInfo(true);outputfile.close();new File("E:/temp/afile.out").delete();}}@Overridepublic void run() {try {outputfile.write(content); // append content} catch (IOException e) {e.printStackTrace();}} }正如您在第25行中看到的那樣,基準(zhǔn)測(cè)試將對(duì)普通文件執(zhí)行一次寫入操作。 這些是結(jié)果:
Test: Performance_Benchmark_ConventionalFileAccessExample_1 Warming up ... EPSILON:20:TESTTIME:1000:ACTTIME:1014:LOOPS:365947 EPSILON:20:TESTTIME:1000:ACTTIME:1014:LOOPS:372298 Starting test intervall ... EPSILON:20:TESTTIME:1000:ACTTIME:1000:LOOPS:364706 EPSILON:20:TESTTIME:1000:ACTTIME:1014:LOOPS:368309 EPSILON:20:TESTTIME:1000:ACTTIME:1014:LOOPS:370288 EPSILON:20:TESTTIME:1000:ACTTIME:1001:LOOPS:364908 EPSILON:20:TESTTIME:1000:ACTTIME:1014:LOOPS:370820 Mean: 367.806,2 Std. Deviation: 2.588,665 Total started thread count: 12 Peak thread count: 6 Deamon thread count: 4 Thread count: 5以下代碼段是另一個(gè)基準(zhǔn),該基準(zhǔn)也向異步文件通道發(fā)出寫操作(第25行):
public class Performance_Benchmark_AsynchronousFileChannel_1 implements Runnable {private static AsynchronousFileChannel outputfile;private static int fileindex = 0;public static void main(String[] args) throws InterruptedException, IOException {try {System.out.println("Test: " + Performance_Benchmark_AsynchronousFileChannel_1.class.getSimpleName());outputfile = AsynchronousFileChannel.open(Paths.get("E:/temp/afile.out"), StandardOpenOption.WRITE,StandardOpenOption.CREATE, StandardOpenOption.DELETE_ON_CLOSE);Average average = new PerformanceHarness().calculatePerf(new PerformanceChecker(1000,new Performance_Benchmark_AsynchronousFileChannel_1()), 5);System.out.println("Mean: " + DecimalFormat.getInstance().format(average.mean()));System.out.println("Std. Deviation: " + DecimalFormat.getInstance().format(average.stddev()));} catch (Exception e) {e.printStackTrace();} finally {new SystemInformation().printThreadInfo(true);outputfile.close();}}@Overridepublic void run() {outputfile.write(ByteBuffer.wrap("Hello".getBytes()), fileindex++ * 5);} }這是我的機(jī)器上上述基準(zhǔn)測(cè)試的結(jié)果:
Test: Performance_Benchmark_AsynchronousFileChannel_1 Warming up ... EPSILON:20:TESTTIME:1000:ACTTIME:1015:LOOPS:42667 EPSILON:20:TESTTIME:1000:ACTTIME:1015:LOOPS:193351 Starting test intervall ... EPSILON:20:TESTTIME:1000:ACTTIME:1015:LOOPS:191268 EPSILON:20:TESTTIME:1000:ACTTIME:1015:LOOPS:186916 EPSILON:20:TESTTIME:1000:ACTTIME:1014:LOOPS:189842 EPSILON:20:TESTTIME:1000:ACTTIME:1014:LOOPS:191103 EPSILON:20:TESTTIME:1000:ACTTIME:1015:LOOPS:192005 Mean: 190.226,8 Std. Deviation: 1.795,733 Total started thread count: 17 Peak thread count: 11 Deamon thread count: 9 Thread count: 10由于上面的代碼片段執(zhí)行相同的操作,因此可以肯定地說(shuō)異步文件通道不一定比常規(guī)I / O更快。 我認(rèn)為這是一個(gè)有趣的結(jié)果。 在單線程基準(zhǔn)測(cè)試中很難將常規(guī)I / O和NIO.2相互比較。 引入NIO.2是為了在高度并發(fā)的場(chǎng)景中提供I / O技術(shù)。 因此,詢問(wèn)更快的速度(NIO或常規(guī)I / O)并不是一個(gè)正確的問(wèn)題。 合適的問(wèn)題是:什么是“更多并發(fā)”? 但是,就目前而言,以上結(jié)果表明:
當(dāng)只有一個(gè)線程發(fā)出I / O操作時(shí),請(qǐng)考慮使用常規(guī)I / O。
現(xiàn)在就足夠了。 我已經(jīng)解釋了基本概念,還指出了常規(guī)I / O仍然存在。 在第二篇文章中,我將介紹使用默認(rèn)異步文件通道時(shí)可能遇到的一些問(wèn)題。 我還將展示如何通過(guò)應(yīng)用一些更可行的設(shè)置來(lái)避免這些問(wèn)題。
應(yīng)用自定義線程池
異步文件處理并不是高性能的綠卡。 在上一篇文章中,我證明了常規(guī)I / O可以比異步通道更快。 應(yīng)用NIO.2文件通道時(shí),還需要了解一些其他重要事實(shí)。 默認(rèn)情況下,在NIO.2文件通道中執(zhí)行所有異步I / O任務(wù)的Iocp類由所謂的“緩存”線程池支持。 這是一個(gè)線程池,可以根據(jù)需要?jiǎng)?chuàng)建新線程,但是會(huì)在可用時(shí)重用以前構(gòu)造的線程。 查看Iocp持有的ThreadPool類的代碼。
public class ThreadPool { ...private static final ThreadFactory defaultThreadFactory = new ThreadFactory() {@Overridepublic Thread newThread(Runnable r) {Thread t = new Thread(r);t.setDaemon(true);return t;}}; ...static ThreadPool createDefault() {...ExecutorService executor =new ThreadPoolExecutor(0, Integer.MAX_VALUE,Long.MAX_VALUE, TimeUnit.MILLISECONDS,new SynchronousQueue<Runnable>(),threadFactory);return new ThreadPool(executor, false, initialSize);} ... }默認(rèn)通道組中的線程池被構(gòu)造為ThreadPoolExecutor,最大線程數(shù)為Integer.MAX_VALUE,保持時(shí)間為L(zhǎng)ong.MAX_VALUE。 線程由線程工廠創(chuàng)建為守護(hù)程序線程。 如果所有線程都忙,則使用同步移交隊(duì)列來(lái)觸發(fā)線程創(chuàng)建。 此配置存在多個(gè)問(wèn)題:
在我的其他博客中,我解釋了為什么無(wú)限制線程池會(huì)引起麻煩。 因此,如果您使用異步文件通道,則可以選擇使用自定義線程池而不是默認(rèn)線程池。 以下代碼段顯示了示例自定義設(shè)置。
ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(2500)); pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); AsynchronousFileChannel outputfile = AsynchronousFileChannel.open(Paths.get(FILE_NAME), new HashSet<Standardopenoption> (Arrays.asList(StandardOpenOption.WRITE, StandardOpenOption.CREATE)), pool);AsynchronousFileChannel的Javadoc指出,自定義執(zhí)行程序應(yīng)“至少[...]支持無(wú)限制的工作隊(duì)列,并且不應(yīng)在execute方法的調(diào)用者線程上運(yùn)行任務(wù)”。 這是一個(gè)冒險(xiǎn)的說(shuō)法,只有在資源不成問(wèn)題的情況下才是合理的,這種情況很少發(fā)生。 對(duì)于異步文件通道,請(qǐng)使用有限線程池。 您不會(huì)遇到線程太多的問(wèn)題,也無(wú)法用工作隊(duì)列任務(wù)來(lái)充斥您的堆。 在上面的示例中,您有五個(gè)線程執(zhí)行異步I / O任務(wù),并且工作隊(duì)列可容納2500個(gè)任務(wù)。 如果超過(guò)了容量限制,則拒絕執(zhí)行處理程序?qū)?shí)現(xiàn)CallerRunsPolicy,在該處客戶端必須同步執(zhí)行寫任務(wù)。 因?yàn)楣ぷ髫?fù)載被“推回”到客戶端并同步執(zhí)行,所以這可能(極大地)降低系統(tǒng)性能。 但是,它也可以使您免受結(jié)果無(wú)法預(yù)測(cè)的更嚴(yán)重的問(wèn)題的困擾。 最佳做法是使用有界線程池并保持線程池大小可配置,以便您可以在運(yùn)行時(shí)進(jìn)行調(diào)整。 同樣,要了解有關(guān)可靠的線程池設(shè)置的更多信息,請(qǐng)參閱我的其他博客條目。
具有同步移交隊(duì)列和未限制最大線程池大小的線程池可能會(huì)激進(jìn)地創(chuàng)建新線程,因此,通過(guò)消耗(PC寄存器和Java堆棧)JVM的運(yùn)行時(shí)內(nèi)存,可能會(huì)嚴(yán)重?fù)p害系統(tǒng)穩(wěn)定性。 異步任務(wù)的“時(shí)間越長(zhǎng)”(經(jīng)過(guò)的時(shí)間),您越有可能遇到此問(wèn)題。
具有無(wú)限制工作隊(duì)列和固定線程池大小的線程池可以激進(jìn)地創(chuàng)建新的任務(wù)和對(duì)象,從而通過(guò)過(guò)多的垃圾回收活動(dòng)消耗堆內(nèi)存和CPU,從而嚴(yán)重?fù)p害系統(tǒng)穩(wěn)定性。 異步任務(wù)越大(大小)越長(zhǎng)(經(jīng)過(guò)時(shí)間),您越有可能遇到此問(wèn)題。
這就是將自定義線程池應(yīng)用于異步文件通道的全部?jī)?nèi)容。 我在本系列的下一篇博客中將介紹如何安全地關(guān)閉異步通道而不丟失數(shù)據(jù)。
參考:測(cè)試平臺(tái)上的Java 7#7:NIO.2文件通道–第1部分–簡(jiǎn)介,測(cè)試平臺(tái)上的Java 7#8:NIO.2文件通道–第2部分–應(yīng)用來(lái)自我們JCG合作伙伴 Niklas的自定義線程池。
翻譯自: https://www.javacodegeeks.com/2012/04/java-7-8-nio2-file-channels-on-test.html
總結(jié)
以上是生活随笔為你收集整理的Java 7#8:测试台上的NIO.2文件通道的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 魅族 安卓(安卓n魅族)
- 下一篇: 集成Spring和JavaServer