Java 7:在不丟失數據的情況下關閉NIO.2文件通道

關閉異步文件通道可能非常困難。 如果您將I / O任務提交到異步通道,則需要確保正確執行了任務。 實際上,出于多種原因,這對于異步通道可能是一個棘手的要求。 默認的通道組使用守護進程線程作為工作線程,這不是一個好選擇,因為如果JVM退出,這些線程就會被放棄。 如果將自定義線程池執行程序與非守護線程一起使用,則需要自己管理線程池的生命周期。 如果不這樣做,當主線程退出時,線程僅保持活動狀態。 因此,JVM實際上根本不會退出,您可以執行的操作是殺死JVM。

關閉異步通道時,另一個問題在AsynchronousFileChannel的javadoc中提到:“在通道打開時關閉執行程序服務會導致未指定的行為。” 這是因為close()上的操作AsynchronousFileChannel問題的任務是模擬與掛起的I / O操作(在同一個線程池)的故障相關的執行服務AsynchronousCloseException 。 因此,如果您在先前關閉關聯的執行程序服務時在異步文件通道實例上執行close() ,則會得到RejectedExecutionException

綜上所述,安全配置文件通道并關閉該通道的建議方法如下:

public class SimpleChannelClose_AsynchronousCloseException {private static final String FILE_NAME = "E:/temp/afile.out";private static AsynchronousFileChannel outputfile;private static AtomicInteger fileindex = new AtomicInteger(0);private static ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());public static void main(String[] args) throws InterruptedException, IOException, ExecutionException {outputfile = AsynchronousFileChannel.open(Paths.get(FILE_NAME),new HashSet<StandardOpenOption>(Arrays.asList(StandardOpenOption.WRITE, StandardOpenOption.CREATE,StandardOpenOption.DELETE_ON_CLOSE)), pool);List<Future<Integer>> futures = new ArrayList<>();for (int i = 0; i < 10000; i++) {futures.add(outputfile.write(ByteBuffer.wrap("Hello".getBytes()), fileindex.getAndIncrement() * 5));}outputfile.close();pool.shutdown();pool.awaitTermination(60, TimeUnit.SECONDS);for (Future<Integer> future : futures) {try {future.get();} catch (ExecutionException e) {System.out.println("Task wasn't executed!");}}}
}

在第6和7行中定義了定制線程池執行程序服務。在第10至13行中定義了文件通道。在第18至20行中,異步通道以有序方式關閉。 首先關閉通道本身,然后關閉執行程序服務,最后最重要的一點是線程等待線程池執行程序的終止。

盡管這是使用自定義執行程序服務關閉通道的安全方法,但還是引入了新問題。 客戶端提交了異步寫入任務(第16行),并且可能希望確保一旦成功提交了這些任務,這些任務肯定會被執行。 始終不等待Future.get()返回(第23行),因為在許多情況下,這將導致*異步*文件通道adurdum。 上面的代碼段將返回很多“未執行任務!” 消息導致將寫操作提交到通道后立即關閉通道(第18行)。 為了避免這種“數據丟失”,您可以實現自己的CompletionHandler并將其傳遞給請求的寫操作。

public class SimpleChannelClose_CompletionHandler {
...public static void main(String[] args) throws InterruptedException, IOException, ExecutionException {
...outputfile.write(ByteBuffer.wrap("Hello".getBytes()), fileindex.getAndIncrement() * 5, "", defaultCompletionHandler);
...}private static CompletionHandler<integer, string=""> defaultCompletionHandler = new CompletionHandler<Integer, String>() {@Overridepublic void completed(Integer result, String attachment) {// NOP}@Overridepublic void failed(Throwable exc, String attachment) {System.out.println("Do something to avoid data loss ...");}};
}

CompletionHandler.failed()方法(第16行)在任務處理期間捕獲任何運行時異常。 您可以在此處實施任何補償代碼,以避免數據丟失。 處理關鍵任務數據時,最好使用CompletionHandler 。 但是*仍然*還有另一個問題。 客戶端可以提交任務,但是他們不知道池是否將成功處理這些任務。 在這種情況下,成功表示已提交的字節實際上到達了目的地(硬盤上的文件)。 如果您想確保所有提交的任務在關閉前都已得到實際處理,則會有些棘手。 您需要一個“優美的”關閉機制,該機制要等到工作隊列為空時才*實際上*先關閉通道和關聯的執行程序服務(使用標準生命周期方法無法實現)。

引入GracefulAsynchronousChannel

我的最后一個片段介紹了GracefulAsynchronousFileChannel 。 您可以在我的Git存儲庫中獲取完整的代碼。 該通道的行為是這樣的:保證處理所有成功提交的寫操作,如果通道準備關閉,則拋出NonWritableChannelException 。 實現該行為需要兩件事。 首先,您需要在ThreadPoolExecutor的擴展中實現afterExecute() ,該擴展在隊列為空時發送信號。 這就是DefensiveThreadPoolExecutor所做的。

private class DefensiveThreadPoolExecutor extends ThreadPoolExecutor {public DefensiveThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,LinkedBlockingQueue<Runnable> workQueue, ThreadFactory factory, RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, factory, handler);}/*** "Last" task issues a signal that queue is empty after task processing was completed.*/@Overrideprotected void afterExecute(Runnable r, Throwable t) {if (state == PREPARE) {closeLock.lock(); // only one thread will pass when closer thread is awaiting signaltry {if (getQueue().isEmpty() && state < SHUTDOWN) {System.out.println("Issueing signal that queue is empty ...");isEmpty.signal();state = SHUTDOWN; // -> no other thread can issue empty-signal}} finally {closeLock.unlock();}}super.afterExecute(r, t);}
}

afterExecute()方法(第12行)在每個處理的任務之后由處理給定任務的線程執行。 該實現在第18行中發送isEmpty信號。第二個需要您優雅地關閉一個通道的部分是AsynchronousFileChannelclose()方法的自定義實現。

/*** Method that closes this file channel gracefully without loosing any data.*/
@Override
public void close() throws IOException {AsynchronousFileChannel writeableChannel = innerChannel;System.out.println("Starting graceful shutdown ...");closeLock.lock();try {state = PREPARE;innerChannel = AsynchronousFileChannel.open(Paths.get(uri),new HashSet<StandardOpenOption>(Arrays.asList(StandardOpenOption.READ)), pool);System.out.println("Channel blocked for write access ...");if (!pool.getQueue().isEmpty()) {System.out.println("Waiting for signal that queue is empty ...");isEmpty.await();System.out.println("Received signal that queue is empty ... closing");} else {System.out.println("Don't have to wait, queue is empty ...");}} catch (InterruptedException e) {Thread.interrupted();throw new RuntimeException("Interrupted on awaiting Empty-Signal!", e);} catch (Exception e) {throw new RuntimeException("Unexpected error" + e);} finally {closeLock.unlock();writeableChannel.force(false);writeableChannel.close(); // close the writable channelinnerChannel.close(); // close the read-only channelSystem.out.println("File closed ...");pool.shutdown(); // allow clean up tasks from previous close() operation to finish safelytry {pool.awaitTermination(1, TimeUnit.MINUTES);} catch (InterruptedException e) {Thread.interrupted();throw new RuntimeException("Could not terminate thread pool!", e);}System.out.println("Pool closed ...");}
}

研究該代碼一段時間。 有趣的位在第11行中,其中的innerChannel被只讀通道替換。 這將導致任何后續的異步寫入請求均由于NonWritableChannelException而失敗。 在第16行中, close()方法等待isEmpty信號發生。 在上一個寫任務之后發送此信號時, close()方法將繼續執行有序的關閉過程(第27頁及其后的內容)。 基本上,代碼在文件通道和關聯的線程池之間添加了共享的生命周期狀態。 這樣,兩個對象都可以在關閉過程中進行通信,并避免數據丟失。

這是使用GracefulAsynchronousFileChannel的日志記錄客戶端。

public class MyLoggingClient {private static AtomicInteger fileindex = new AtomicInteger(0);private static final String FILE_URI = "file:/E:/temp/afile.out";public static void main(String[] args) throws IOException {new Thread(new Runnable() { // arbitrary thread that writes stuff into an asynchronous I/O data sink@Overridepublic void run() {try {for (;;) {GracefulAsynchronousFileChannel.get(FILE_URI).write(ByteBuffer.wrap("Hello".getBytes()),fileindex.getAndIncrement() * 5);}} catch (NonWritableChannelException e) {System.out.println("Deal with the fact that the channel was closed asynchronously ... "+ e.toString());} catch (Exception e) {e.printStackTrace();}}}).start();Timer timer = new Timer(); // asynchronous channel closertimer.schedule(new TimerTask() {public void run() {try {GracefulAsynchronousFileChannel.get(FILE_URI).close();long size = Files.size(Paths.get("E:/temp/afile.out"));System.out.println("Expected file size (bytes): " + (fileindex.get() - 1) * 5);System.out.println("Actual file size (bytes): " + size);if (size == (fileindex.get() - 1) * 5)System.out.println("No write operation was lost!");Files.delete(Paths.get("E:/temp/afile.out"));} catch (IOException e) {e.printStackTrace();}}}, 1000);}
}

客戶端啟動兩個線程,一個線程在無限循環中(第6行以下)發出寫操作。 在處理一秒鐘后,另一個線程異步關閉文件通道(第25 ff行)。 如果運行該客戶端,那么將產生以下輸出:

Starting graceful shutdown ...
Deal with the fact that the channel was closed asynchronously ... java.nio.channels.NonWritableChannelException
Channel blocked for write access ...
Waiting for signal that queue is empty ...
Issueing signal that queue is empty ...
Received signal that queue is empty ... closing
File closed ...
Pool closed ...
Expected file size (bytes): 400020
Actual file size (bytes): 400020
No write operation was lost!

輸出顯示參與線程的有序關閉過程。 日志記錄線程需要處理通道異步關閉的事實。 處理排隊的任務后,將關閉通道資源。 沒有數據丟失,客戶端發出的所有內容均已真正寫入文件目標位置。 在這種正常的關閉過程中,沒有AsynchronousClosedExceptionRejectedExecutionException

這就是安全關閉異步文件通道的全部方法。 完整的代碼在我的Git存儲庫中 。 希望您喜歡它。 期待您的評論。

參考:來自我們JCG合作伙伴 Niklas的“ Java 7:關閉NIO.2文件通道而不會丟失數據”。

翻譯自: https://www.javacodegeeks.com/2012/05/java-7-closing-nio2-file-channels.html

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

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

相關文章

JavaScript封裝方法,兼容參數類型為Number和String

/*** 依據Kind確定跳轉到目標列表頁面。* param kind*/function gobackByKind(kind) {var kindStr String(kind);switch(kindStr){case "1"://跳轉到客戶列表頁面window.location.href/biz/customer/list;break;case "2"://跳轉到代理機構列表頁面window.…

#獲得請求來源ip_以太網數據包TCP、IP、ICMP、UDP、ARP協議頭結構詳解

以太網首部目地MAC地址(8字節)源MAC地址(8字節)類型(2字節)1、IP頭的結構版本(4位)頭長度(4位)服務類型(8位)封包總長度(16位)封包標識(16位)標志(3位)片斷偏移地址(13位)存活時間(8位)協議(8位)校驗和(16位)來源IP地址(32位)目的IP地址(32位)選項(可選)填充(可選)數據(1)字節和…

團隊項目第二次沖刺Ⅶ

今天將整體代碼的編碼方式改了&#xff0c;作業模塊基本修改完成 遇到的問題是對于添加問答模塊無從下手轉載于:https://www.cnblogs.com/brucekun/p/5573312.html

編寫Play 2的模塊,第2部分:攔截器

在本教程的第一部分中&#xff0c;我們介紹了創建&#xff0c;發布和調用模塊的基本知識。 我們創建的模塊并沒有真正做很多事情&#xff0c;因此現在是時候使用Play的某些功能來擴展功能了。 1.攔截器 攔截器使您可以攔截對控制器的調用&#xff0c;并增強或阻止其行為。 在第…

c# ef報錯_C# EF調用MySql出現“未將對象引用設置到對象的實例”錯誤解決方案

C# EF調用MySql出現“未將對象引用設置到對象的實例”錯誤解決方案---修改步驟---1.打開Nuget管理包&#xff0c;把Mysql.Data替換為6.10.0以下任意版本。這里選擇的是6.8.82.修改完畢后&#xff0c;繼續把Mysql.Data.Entity也修改為對應版本6.8.8。3.安裝完成后可以看到App.Co…

js格式化時間

Date.prototype.format function(fmt) {var o { "M" : this.getMonth()1, //月份 "d" : this.getDate(), //日 "h" : this.getHours(), //小時 "m" : this.getMinu…

PHP---函數

一.函數定義的四個要素 返回類型&#xff0c;函數名&#xff0c;參數列表&#xff0c;函數體 //1.最簡單的定義方式/*function show(){ echo "hello";}show();*///2.有參數的函數定義/*function show($a){ echo $a;}show("bbbbb");*///3.有默認值的函數定義…

ServletRequest startAsync()的有用性有限

前段時間我遇到了Servlet 3.0中AsyncContext.start&#xff08;…&#xff09;的目的是什么&#xff1f; 題。 引用上述方法的Javadoc &#xff1a; 使容器調度線程&#xff08;可能從托管線程池中&#xff09;運行指定的Runnable 。 提醒大家&#xff0c; AsyncContext是Servl…

mysql所支持的比較運算符_mysql比較運算符有哪些?Mysql比較運算符詳解

比較運算符可用于比較數字和字符串。今天發一篇Mysql比較運算符詳解&#xff0c;希望對初學者有所幫助&#xff0c;雖然現在流行NoSQL&#xff0c;但是MYSQL還是很有用的&#xff0c;數字作為浮點值進行比較&#xff0c;字符串以不區為例進行比較&#xff0c;運算符用于比較表達…

數據結構0類模板的使用

類模板的使用 #include <iostream> #include <conio.h> #include <string> #define N 3 using namespace std;template <class numtype> class Swap{public :Swap(numtype a,numtype b){xa;yb;}numtype ___(){tempx;xy;ytemp;return x;}//testnumtype …

JavaScript 函數

函數 由于JavaScript的函數也是一個對象&#xff0c;所以類似function abs(v){}函數實際上是一個函數對象&#xff0c;而函數名abs可以視為指向該函數的變量。 因此&#xff0c;第二種定義函數的方式如下&#xff1a; var abs function (x) {if (x > 0) {return x;} else {…

Http Invoker的Spring Remoting支持

Spring HTTP Invoker是Java到Java遠程處理的重要解決方案。 該技術使用標準的Java序列化機制通過HTTP公開服務&#xff0c;并且可以被視為替代解決方案&#xff0c;而不是Hessian和Burlap中的自定義序列化。 而且&#xff0c;它僅由Spring提供&#xff0c;因此客戶端和服務器應…

mysql 日期列表_MySQL 生成日期表

1、創建一個num表&#xff0c;用來存儲數字0~9CREATE TABLE num (i int);2、在num表中生成0~9INSERT INTO num (i) VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9);3、生成一個存儲日期的表&#xff0c;datalist是字段名CREATE TABLE if not exists calendar(dateli…

學習后綴自動機想法

小序&#xff1a;學習后綴自動機是要有耐心的&#xff0c;clj的論文自己看真心酸爽&#xff01;&#xff08;還是自己太弱&#xff0c;ls&#xff0c;oyzx好勁啊&#xff0c;狂膜不止&#xff09; 剛剛在寫博客之前又看了篇論文&#xff0c;終于看懂了&#xff0c;好開心 正文&…

【BZOJ】3575: [Hnoi2014]道路堵塞

題目鏈接&#xff1a;http://www.lydsy.com/JudgeOnline/problem.php?id3575 大概的做法是&#xff0c;按照順序枚舉每一條要刪去的邊&#xff0c;(假設當前點為$u$&#xff0c;在最短路徑上的下一個點是$v$)然后強制不走${u->v}$這條邊&#xff0c;將$u$入隊&#xff0c;做…

結合使用slf4j和Logback教程

在當前文章中&#xff0c;我將向您展示如何配置您的應用程序以使用slf4j和logback作為記錄器解決方案。 Java簡單日志記錄外觀&#xff08;slf4j&#xff09;是各種日志記錄框架的簡單外觀&#xff0c;例如JDK日志記錄&#xff08;java.util.logging&#xff09;&#xff0c;lo…

mysql 分組top_MySQL:如何查詢出每個分組中的 top n 條記錄?

問題描述需求&#xff1a;查詢出每月 order_amount(訂單金額) 排行前3的記錄。例如對于2019-02&#xff0c;查詢結果中就應該是這3條&#xff1a;解決方法MySQL 5.7 和 MySQL 8.0 有不同的處理方法。1. MySQL 5.7我們先寫一個查詢語句。根據 order_date 中的年、月&#xff0c;…

ACM第四站————最小生成樹(普里姆算法)

對于一個帶權的無向連通圖&#xff0c;其每個生成樹所有邊上的權值之和可能不同&#xff0c;我們把所有邊上權值之和最小的生成樹稱為圖的最小生成樹。 普里姆算法是以其中某一頂點為起點&#xff0c;逐步尋找各個頂點上最小權值的邊來構建最小生成樹。 其中運用到了回溯&#…

利用jenkins的api來完成相關工作流程的自動化

[本文出自天外歸云的博客園] 背景 1. 實際工作中涉及到安卓客戶端方面的測試&#xff0c;外推或運營部門經常會有很多的渠道&#xff0c;而每個渠道都對應著一個app的下載包&#xff0c;這些渠道都記錄在安卓項目下的一個渠道列表文件中。外推或運營部門經常會有新的渠道產生&a…

擁有成本分析:Oracle WebLogic Server與JBoss

Crimson Consulting Group 撰寫的非常有趣的白皮書 &#xff0c;比較了Weblogic和JBoss之間的擁有成本 。 盡管JBoss是免費的&#xff0c;但該白皮書卻嚴肅地宣稱&#xff0c;從長遠來看&#xff0c;Weblogic更便宜。 盡管此研究是由Oracle贊助的&#xff0c;但它看起來非常嚴肅…