Java中的文件鎖到底是怎麼回事?

2021-02-13 Linkoffer

點擊上方「linkoffer」,

選擇關注公眾號高薪職位第一時間送達

來源 | https://urlify.cn/jumuiq

當讀寫文件時,需要確保有適當的文件鎖定機制,來保證基於並發I/O應用程式的數據完整性。

本教程中, 我們將介紹使用 Java NIO 庫實現這一點的各種方法。

# 文件鎖簡介

「一般來說,有兩種鎖」:

簡單地說,在寫操作完成時,獨佔鎖防止所有其他操作(包括讀操作)。

相反,共享鎖允許多個進程同時讀取。讀鎖的目的是防止另一個進程獲取寫鎖。通常,處於一致狀態的文件確實應該被任何進程讀取。

在下一節中,我們將看到Java如何處理這些類型的鎖。

# Java中的文件鎖

Java NIO庫支持在作業系統級別鎖定文件。FileChannel 中的lock() 和*tryLock()*方法就是為了這個而存在。

我們可以通過 FileInputStream, FileOutputStream,RandomAccessFile 來獲取FileChannel,三者均可通過 getChannel() 方法返回 FileChannel對象.

或者, 我們可以直接通過靜態方法 open 來創建 FileChannel  :

 try (FileChannel channel = FileChannel.open(path, openOptions)) {    }


接下來,我們將回顧在Java中獲取獨佔鎖和共享鎖的不同方式。要了解有關文件通道的更多信息,請查看[Guide to Java FileChanne 教程。

# 獨佔鎖

正如我們已經了解到的,在寫入文件時,「我們可以使用獨佔鎖」防止其他進程讀取或寫入文件。

我們通過調用 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)) {      }


1. 使用 FileOutputStream 的獨佔鎖

從 FileOutputStream 創建的 FileChannel 是可寫的。因此,我們可以獲得一個獨佔鎖:

try (FileOutputStream fileOutputStream = new FileOutputStream("/tmp/testfile.txt");      FileChannel channel = fileOutputStream.getChannel();      FileLock lock = channel.lock()) {      }


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()) {      }


如果我們以只讀模式打開文件,並嘗試向其通道進行寫入操作,將會拋出 NonWritableChannelException 異常。

3.獨佔鎖依賴於可讀的 FileChannel

如前所述,獨佔鎖需要一個可寫通道。因此,我們無法通過從 FileInputStream 創建的 FileChannel 獲得獨佔鎖:

try (RandomAccessFile file = new RandomAccessFile("/tmp/testfile.txt", "rw");       FileChannel channel = file.getChannel();       FileLock lock = channel.lock()) {      }hannel.lock()) {     


在上面的例子中,lock() 方法將拋出一個 nonwriteablechannelexception 。實際上,這是因為我們正在對一個創建只讀通道的 FileInputStream調用 getChannel。這個例子只是為了證明我們不能寫到一個不可寫的通道。事實上,我們不會捕捉並重新拋出異常。

#  共享鎖

記住,共享鎖也稱為讀 鎖。因此,要獲得讀鎖,我們必須使用可讀的文件通道。

這樣的 FileChannel 可以通過調用 FileInputStream 或 RandomAccessFile 上的 getChannel() 方法獲得。同樣,另一個選項是使用 FileChannel 類的靜態 open 方法。在這種情況下,我們將第二個參數設置為 StandardOpenOption.READ 。

try (RandomAccessFile file = new RandomAccessFile("/tmp/testfile.txt", "rw");       FileChannel channel = file.getChannel();       FileLock lock = channel.lock()) {      }


這裡要注意的一點是,我們選擇通過調用 lock(0, Long.MAX_VALUE, true) 來鎖定整個文件。通過將前兩個參數更改為不同的值,我們還可以只鎖定文件的特定區域。對於共享鎖,第三個參數必須設置為true。

為了簡單起見,我們將在下面的所有示例中鎖定整個文件,但請記住,我們始終可以鎖定文件的特定區域。

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)) {      }


在上面的代碼片段中,將成功調用通道上的 lock() 。這是因為共享鎖只要求通道是可讀的就行。

2. 使用 RandomAccessFile中的共享鎖
這次,我們只需要使用 」讀」 權限打開文件即可:
try (RandomAccessFile file = new RandomAccessFile("/tmp/testfile.txt", "r");      FileChannel channel = file.getChannel();      FileLock lock = channel.lock(0, Long.MAX_VALUE, true)) {       }


在本例中,我們創建了一個具有讀取權限的RandomAccessFile對象,然後從中創建一個可讀通道,從而創建一個共享鎖。

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)) {      } catch (NonWritableChannelException e) {      }


在本例中,調用 lock() 嘗試獲取從 FileOutputStream 創建的通道上的共享鎖。這樣的通道是只寫的。它不能滿足通道必須可讀的需要。這將觸發一個NonWritableChannelException。

同樣,這段代碼只是為了證明我們不能從一個不可讀的通道中讀取。

# 思考

實際上,使用文件鎖是困難的;鎖定機制是不可移植的。我們需要考慮到這一點來設計鎖定邏輯。

在POSIX系統中,鎖是建議性的。讀取或寫入給定文件的不同進程必須就鎖定協議達成一致。這將確保文件的完整性。作業系統本身不會強制任何鎖定。

在Windows上,除非允許共享,否則鎖將是獨佔的。討論作業系統特定機制的優點或缺點超出了本文的討論範圍。然而,在實現鎖定機制時,了解這些細微差別很重要。

# 總結

在本教程中,我們回顧了在Java中獲取文件鎖的幾種不同選項。

首先,我們首先了解兩種主要的鎖定機制,以及Java NIO庫如何促進鎖定文件。

然後,我們瀏覽了一系列簡單的示例,這些示例顯示我們可以在應用程式中獲得獨佔和共享鎖。我們還研究了使用文件鎖時可能遇到的典型異常類型。 

相關焦點

  • java安全編碼指南之:文件和共享目錄的安全性
    簡介java程序是跨平臺的,可以運行在windows也可以運行在linux。但是平臺不同,平臺中的文件權限也是不同的。
  • 每日一課 | 從Java中的文件讀取對象
    在此示例中,您將學習如何從保存的文件中讀取對象或如何反序列化序列化的文件
  • 使用Lock鎖:java多線程安全問題解決方案之Lock鎖
    今天我們來學習一下Lock鎖,它是java 1.5之後出現的接口 java.util.concurrent.locks.Lock接口LockLock接口中的方法:void lock()獲取鎖。void unlock() 釋放鎖。Lock鎖可以提升多個線程的讀寫效率。
  • 鎖車後第二天來開車門,發現車門是沒鎖的,這是怎麼回事?
    鎖車後第二天來開車門,發現車門是沒鎖的,這是怎麼回事?下車鎖車是我們必要的一件事情,但是很多的時候,會出現鎖車後第二天來開車門,發現車門是沒鎖的,那麼出現這樣的事情,是怎麼回事呢?我們在使用車輛鑰匙的過程中,一定要避免以下的一些情況,那就是對於智通遙控鑰匙要避免與一些強磁場接觸,比如,高壓線、發射塔等存在強磁場信號的地方,要儘量避免車輛停放,因為智能鑰匙使用低強度無線電波,因此在有磁場幹擾的情況下可能無法正常工作。另外的話也可以看看遙控器是否有過進水或者短路,排除後看看電池是否接觸不好。
  • Java讀取和寫入txt文件
    1 問題描述對於java的讀取和寫入txt一直心存疑惑,隨著知識的積累,又重新進行學習,對java的文件讀寫理解更加深刻,在這裡將自己的小小經驗總結分享給大家。下面是大家了解java流的一個基本框架。2 問題分析在java中,java的讀寫操作(輸入輸出)可以用「流」這個概念來表示,輸入和輸出功能是Java對程序處理數據能力的提高, java的讀寫操作又分為兩種:字符流和字節流。Java以流的形式處理數據。流是一組有序的數據序列,根據操作的類型,分為輸入流和輸出流。
  • Java Web安全 || Java基礎 · Java IO/NIO多種讀寫文件方式
    我們通常讀寫文件都是使用的阻塞模式,與之對應的也就是java.io.FileSystem。java.io.FileInputStream類提供了對文件的讀取功能,Java的其他讀取文件的方法基本上都是封裝了java.io.FileInputStream類,比如:java.io.FileReader。
  • Java鎖的那些事兒
    Java中的鎖機制主要分為 Lock和 Synchronized,本文主要分析Java鎖機制的使用和實現原理,按照Java鎖使用、JDK中鎖實現、系統層鎖實現的順序來進行分析,話不多說,let's go~一、Java鎖使用在Lock接口出現之前,Java程序是靠synchronized關鍵字實現鎖功能的,而JavaSE 5之後,並發包中新增了Lock接口(以及相關實現類)
  • JVM之用Java解析class文件
    解析class文件/前言:身為一個Java程式設計師,怎麼能不了解JVM呢,倘若想學習JVM,那就又必須要了解Class文件,Class之於虛擬機,就如魚之於水,虛擬機因為Class而有了生命。java之所以能夠實現跨平臺,便在於其編譯階段不是將代碼直接編譯為平臺相關的機器語言,而是先編譯成二進位形式的java字節碼,放在Class文件之中,虛擬機再加載Class文件,解析出程序運行所需的內容。每個類都會被編譯成一個單獨的class文件,內部類也會作為一個獨立的類,生成自己的class。
  • 詳細解讀蘋果ID問題丨蘋果ID鎖是怎麼回事
    蘋果id鎖問題是個很複雜的問題,沒有為這個問題犯過難的用戶不會關注這個問題,遇到過的被搞得稀裡糊塗頭都大了。今天奔雷修就給你講述一下蘋果id到底是怎麼回事。可以當做一個雲盤,存儲用戶的資料到蘋果的蘋果伺服器中。3. 可以開啟防盜鎖,定位手機位置。
  • Linux 編程中的文件鎖之 flock
    那在多進程中如何處理文件之間的同步呢?我們看看下面的圖:圖中所示的是兩個進程在無同步的情況下同時更新同一個文件的過程,其主要的操作是:1. 從文件中讀取序號。2. 使用這個序號完成應用程式定義的任務。3. 遞增這個序號並將其寫回文件中。
  • 一文讀懂JAVA類文件結構
    定義: Class文件是一組以8個字節為基礎的單位的二進位流。項目嚴格按照順序緊湊的排列在文件中,沒有添加任何分隔符,這使得整個Class文件中的存儲的內容幾乎全部是程序運行的必要數據,沒有空隙存在。數據類型: 無符號數和表通過以上定義,我們可知,其實我們編寫的java文件,經過javac編譯之後的class文件會以一種二進位流的方式存在,這樣就方便java虛擬機來讀取我們編寫的java文件。
  • Java文件操作——XML文件的讀取
    閱讀目錄一、邂逅XML文件種類是豐富多彩的,XML作為眾多文件類型的一種,經常被用於數據存儲和傳輸。所以XML在現今應用程式中是非常流行的。本文主要講Java解析和生成XML。用於不同平臺、不同設備間的數據共享通信。
  • 「從入門到放棄-Java」並發編程-鎖-synchronized
    簡介上篇【從入門到放棄-Java】並發編程-線程安全中,我們了解到,可以通過加鎖機制來保護共享對象,來實現線程安全。synchronized是java提供的一種內置的鎖機制。通過synchronized關鍵字同步代碼塊。
  • cmd命令行 編譯Java 文件
    如下圖所示:二、帶有包的文件編譯,如工程下的某個Java 文件【注1】由於Java 文件中有package xxx,即包的信息,因此在執行編譯後的Java 文件時就需要帶上包名。表示當前目錄-encoding UTF-8 ,如果需要修改文件的編碼,則添加 -encoding 參數,後面接編碼格式1)執行上述命令後結果如下(在當前路徑下生成demo文件夾,對於工程裡的包名,文件夾下是編譯後的class
  • Java 中15種鎖的介紹:公平鎖,可重入鎖,獨享鎖,互斥鎖,樂觀鎖,分段鎖,自旋鎖等等
    讀寫鎖有三種狀態:讀加鎖狀態、寫加鎖狀態和不加鎖狀態讀寫鎖在Java中的具體實現就是ReadWriteLock一次只有一個線程可以佔有寫模式的讀寫鎖,但是多個線程可以同時佔有讀模式的讀寫鎖。傳統的關係型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨佔鎖就是悲觀鎖思想的實現。
  • 使用面向Java的Lock Analyzen診斷同步和鎖問題
    很多線程可以共享相同的數據對象,但是如果應用程式中控制線程的部分設計得不好,則您可能會遇到鎖爭用問題從而降低性能。  例如,在多線程應用程式中,如果多個線程訪問相同的資源進行讀寫訪問, 則可能會出現線程同步問題。如果一個線程嘗試讀取某個文件,而另一個線程 對其進行寫訪問,則可能會損壞數據。為了解決這個問題,Java 語言允許一個線程獲得對對象的專有鎖, 從而防止其他任何線程訪問它。
  • Java程式設計師必備基礎:Java代碼是怎麼運行的?
    java源文件編譯為class字節碼 類加載器把字節碼加載到虛擬機的方法區。 因此,在運行Java程序之前,需要編譯器把代碼編譯成java虛擬機所能識別的指令程序,這就是Java字節碼,即class文件。 所以,Java代碼運行的第一步是:把Java原始碼編譯成.class 字節碼文件。
  • java序列化反序列化中serialVersionUID到底有什麼用
    序列化\反序列化:java序列化是指把java對象轉換為字節序列的過程,而java反序列化是指把字節序列恢復為java對象的過程。用途:當兩個進程進行遠程通信時,可以相互發送各種類型的數據,包括文本,圖片,音頻,視頻等,而這些數據都會以二進位的形式在網絡上傳送;當兩個java進行進行通信時,要傳送對象,怎麼傳對象,通過序列化與反序列化;永久性保存對象,保存對象的字節序列到本地文件或者資料庫中,實現了數據的持久化;利用序列化實現遠程通信,可以在網絡上傳送對象的字節序列
  • Java中如何高效的讀取大文件
    在java編程中, 我想大部分小夥伴都在使用BufferReader,那麼是否有更高效的讀取方式呢?
  • 「轉載」java架構之路(多線程)synchronized詳解以及鎖的膨脹升級...
    鎖的分類java中我們聽到很多的鎖,什麼顯示鎖,隱式鎖,公平鎖,重入鎖等等,下面我來總結一張圖來供大家學習使用。這次博客我們主要來說我們的隱示鎖,就是我們的無鎖到重量級鎖。6: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 9: ldc #4 // String 只有我拿到鎖啦 11: invokevirtual #5 /