聊一聊Java中的文件锁
點擊上方“朱小廝的博客”,選擇“設為星標”
后臺回復”加群“獲取公眾號專屬群聊入口
1. 概覽
當讀寫文件時,需要確保有適當的文件鎖定機制,來保證基于并發I/O應用程序的數據完整性。
「本教程中, 我們將介紹使用 Java NIO 庫實現這一點的各種方法。」
2. 文件鎖簡介
「一般來說,有兩種鎖」:
獨占鎖——也稱為寫鎖
共享鎖——也稱為讀鎖
簡單地說,在寫操作完成時,獨占鎖防止所有其他操作(包括讀操作)。
相反,共享鎖允許多個進程同時讀取。讀鎖的目的是防止另一個進程獲取寫鎖。通常,處于一致狀態的文件確實應該被任何進程讀取。
在下一節中,我們將看到Java如何處理這些類型的鎖。
3. Java中的文件鎖
Java NIO庫支持在操作系統級別鎖定文件。FileChannel?中的lock()?和*tryLock()*方法就是為了這個而存在。
我們可以通過?FileInputStream,?FileOutputStream,RandomAccessFile?來獲取FileChannel,三者均可通過?getChannel()?方法返回?FileChannel對象.
或者, 我們可以直接通過靜態方法?open?來創建?FileChannel??:
try (FileChannel channel = FileChannel.open(path, openOptions)) {// write to the channel }接下來,我們將回顧在Java中獲取獨占鎖和共享鎖的不同方式。要了解有關文件通道的更多信息,請查看[Guide to Java FileChanne 教程。
4. 獨占鎖
正如我們已經了解到的,在寫入文件時,「我們可以使用獨占鎖」防止其他進程讀取或寫入文件。
我們通過調用?FileChannel?類上的?lock()?或?tryLock())?來獲得獨占鎖。我們還可以使用它們的重載方法:
lock(long position, long size, boolean shared)
tryLock(long position, long size, boolean shared)
在這些情況下,shared參數必須設置為false。
要獲得獨占鎖,必須使用可寫的文件通道。我們可以通過?FileOutputStream?或?RandomAccessFile?的?getChannel()?方法創建它。或者,如前所述,我們可以使用?FileChannel?類的靜態方法:open。我們只需要將第二個參數設置為StandardOpenOption.APPEND?:
try (FileChannel channel = FileChannel.open(path, StandardOpenOption.APPEND)) { // write to channel }4.1. 使用?FileOutputStream?的獨占鎖
從?FileOutputStream?創建的?FileChannel?是可寫的。因此,我們可以獲得一個獨占鎖:
try (FileOutputStream fileOutputStream = new FileOutputStream("/tmp/testfile.txt");FileChannel channel = fileOutputStream.getChannel();FileLock lock = channel.lock()) { // write to the channel }在這里,channel.lock()?要么阻塞直到獲得一個鎖,要么拋出一個異常。例如,如果指定的區域已鎖定,則會引發OverlappingFileLockException。有關可能的異常的完整列表,請參見Javadoc。我們還可以使用?channel.tryLock()?執行非阻塞鎖。如果由于另一個程序持有一個重疊的鎖而無法獲取鎖,則返回null。如果由于任何其他原因未能執行此操作,則會引發相應的異常。
4.2. 使用?RandomAccessFile?的獨占鎖
使用?RandomAccessFile,我們需要設置 [constructor](https://docs.oracle.com/javase/8/docs/api/java/io/RandomAccessFile.html#RandomAccessFile(java.io.File, java.lang.String)) 方法的第二個參數。
在這里,我們將使用讀寫權限打開文件:
try (RandomAccessFile file = new RandomAccessFile("/tmp/testfile.txt", "rw");FileChannel channel = file.getChannel();FileLock lock = channel.lock()) {// write to the channel }如果我們以只讀模式打開文件,并嘗試向其通道進行寫入操作,將會拋出?NonWritableChannelException?異常。
4.3.獨占鎖依賴于可讀的?FileChannel
如前所述,獨占鎖需要一個可寫通道。因此,我們無法通過從?FileInputStream?創建的?FileChannel?獲得獨占鎖:
Path path = Files.createTempFile("foo","txt"); Logger log = LoggerFactory.getLogger(this.getClass()); try (FileInputStream fis = new FileInputStream(path.toFile()); FileLock lock = fis.getChannel().lock()) {// unreachable code } catch (NonWritableChannelException e) {// handle exception }在上面的例子中,lock()?方法將拋出一個?nonwriteablechannelexception?。實際上,這是因為我們正在對一個創建只讀通道的?FileInputStream?調用?getChannel。這個例子只是為了證明我們不能寫到一個不可寫的通道。事實上,我們不會捕捉并重新拋出異常。
5. ?共享鎖
記住,共享鎖也稱為讀?鎖。因此,要獲得讀鎖,我們必須使用可讀的文件通道。
這樣的?FileChannel?可以通過調用?FileInputStream?或?RandomAccessFile?上的?getChannel()?方法獲得。同樣,另一個選項是使用?FileChannel?類的靜態?open?方法。在這種情況下,我們將第二個參數設置為?StandardOpenOption.READ?。
try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ);FileLock lock = channel.lock(0, Long.MAX_VALUE, true)) {// read from the channel }這里要注意的一點是,我們選擇通過調用?lock(0, Long.MAX_VALUE, true)?來鎖定整個文件。通過將前兩個參數更改為不同的值,我們還可以只鎖定文件的特定區域。對于共享鎖,第三個參數必須設置為true。
為了簡單起見,我們將在下面的所有示例中鎖定整個文件,但請記住,我們始終可以鎖定文件的特定區域。
5.1. 使用?FileInputStream?中的共享鎖
從?FileInputStream?獲得的?FileChannel?是可讀的。因此,我們可以獲得一個共享鎖:
try (FileInputStream fileInputStream = new FileInputStream("/tmp/testfile.txt");FileChannel channel = fileInputStream.getChannel();FileLock lock = channel.lock(0, Long.MAX_VALUE, true)) {// read from the channel }在上面的代碼片段中,將成功調用通道上的?lock()?。這是因為共享鎖只要求通道是可讀的就行。
5.2. 使用?RandomAccessFile中的共享鎖
這次,我們只需要使用 ''讀" 權限打開文件即可:
try (RandomAccessFile file = new RandomAccessFile("/tmp/testfile.txt", "r"); FileChannel channel = file.getChannel();FileLock lock = channel.lock(0, Long.MAX_VALUE, true)) {// read from the channel }在本例中,我們創建了一個具有讀取權限的RandomAccessFile對象,然后從中創建一個可讀通道,從而創建一個共享鎖。
5.3. 共享鎖依賴于可讀的?FileChannel
因此,我們無法通過從?FileOutputStream?創建的?FileChannel?獲取共享鎖:
Path path = Files.createTempFile("foo","txt"); try (FileOutputStream fis = new FileOutputStream(path.toFile()); FileLock lock = fis.getChannel().lock(0, Long.MAX_VALUE, true)) {// unreachable code } catch (NonWritableChannelException e) { // handle exception }在本例中,調用?lock()?嘗試獲取從?FileOutputStream?創建的通道上的共享鎖。這樣的通道是只寫的。它不能滿足通道必須可讀的需要。這將觸發一個NonWritableChannelException。
同樣,這段代碼只是為了證明我們不能從一個不可讀的通道中讀取。
6. 思考
實際上,使用文件鎖是困難的;鎖定機制是不可移植的。我們需要考慮到這一點來設計鎖定邏輯。
在POSIX系統中,鎖是建議性的。讀取或寫入給定文件的不同進程必須就鎖定協議達成一致。這將確保文件的完整性。操作系統本身不會強制任何鎖定。
在Windows上,除非允許共享,否則鎖將是獨占的。討論操作系統特定機制的優點或缺點超出了本文的討論范圍。然而,在實現鎖定機制時,了解這些細微差別很重要。
7. 總結
在本教程中,我們回顧了在Java中獲取文件鎖的幾種不同選項。
首先,我們首先了解兩種主要的鎖定機制,以及Java NIO庫如何促進鎖定文件。然后,我們瀏覽了一系列簡單的示例,這些示例顯示我們可以在應用程序中獲得獨占和共享鎖。我們還研究了使用文件鎖時可能遇到的典型異常類型。?
想知道更多?掃描下面的二維碼關注我
后臺回復”加群“獲取公眾號專屬群聊入口
字節跳動2020春季實習生招聘及校招全職補錄全面啟動!
【精彩推薦】
咱們從頭到尾說一次Java垃圾回收
Netty、Kafka中的零拷貝技術到底有多牛?
go為什么這么快?
面試前,我們要復習多少Redis知識?
《深入理解Java虛擬機》第2版挖的坑終于在第3版中被R大填平了
Redis性能問題分析
淺談CAP和Paxos共識算法
朕已閱?
總結
以上是生活随笔為你收集整理的聊一聊Java中的文件锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 你还在 new 对象吗?Java8 通用
- 下一篇: 聊聊 Java 的几把 JVM 级锁