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