Come bloccare un file in Java

1. Panoramica

Durante la lettura o la scrittura di file, dobbiamo assicurarci che siano presenti meccanismi di blocco dei file appropriati. Ciò garantisce l'integrità dei dati nelle applicazioni simultanee basate su I / O.

In questo tutorial, daremo un'occhiata a vari approcci per ottenere questo risultato utilizzando la libreria Java NIO .

2. Introduzione ai blocchi di file

In generale, esistono due tipi di serrature :

    • Blocchi esclusivi, noti anche come blocchi di scrittura
    • Blocchi condivisi, noti anche come blocchi di lettura

In parole povere, un blocco esclusivo impedisce tutte le altre operazioni, comprese le letture, mentre un'operazione di scrittura viene completata.

Al contrario, un blocco condiviso consente a più di un processo di leggere contemporaneamente. Lo scopo di un blocco di lettura è impedire l'acquisizione di un blocco di scrittura da parte di un altro processo. In genere, un file in uno stato coerente dovrebbe essere effettivamente leggibile da qualsiasi processo.

Nella prossima sezione vedremo come Java gestisce questi tipi di blocchi.

3. File Lock in Java

La libreria Java NIO consente di bloccare i file a livello di sistema operativo. I metodi lock () e tryLock () di un FileChannel servono a questo scopo.

Possiamo creare un FileChannel tramite FileInputStream , FileOutputStream o RandomAccessFile . Tutti e tre hanno un metodo getChannel () che restituisce un FileChannel .

In alternativa, possiamo creare un FileChannel direttamente tramite il metodo di apertura statica :

try (FileChannel channel = FileChannel.open(path, openOptions)) { // write to the channel }

Successivamente, esamineremo diverse opzioni per ottenere blocchi esclusivi e condivisi in Java. Per saperne di più sui canali di file, consulta la nostra guida al tutorial Java FileChannel.

4. Serrature esclusive

Come abbiamo già appreso, durante la scrittura su un file, possiamo impedire ad altri processi di leggere o scrivere su di esso utilizzando un blocco esclusivo .

Otteniamo blocchi esclusivi chiamando lock () o tryLock () sulla classe FileChannel . Possiamo anche usare i loro metodi di sovraccarico:

  • lock (long position, long size, boolean shared)
  • tryLock (posizione lunga, dimensione lunga, booleano condiviso)

In questi casi, il parametro condiviso deve essere impostato su false .

Per ottenere un blocco esclusivo, dobbiamo utilizzare un FileChannel scrivibile . Possiamo crearlo tramite i metodi getChannel () di un FileOutputStream o di un RandomAccessFile . In alternativa, come accennato in precedenza, possiamo utilizzare il metodo di apertura statica della classe FileChannel . Tutto ciò di cui abbiamo bisogno è impostare il secondo argomento su StandardOpenOption.APPEND :

try (FileChannel channel = FileChannel.open(path, StandardOpenOption.APPEND)) { // write to channel }

4.1. Blocchi esclusivi che utilizzano un FileOutputStream

Un FileChannel creato da un FileOutputStream è scrivibile. Possiamo, quindi, acquisire una serratura esclusiva:

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

Qui, channel.lock () si bloccherà fino a quando non ottiene un blocco, oppure genererà un'eccezione. Ad esempio, se l'area specificata è già bloccata, viene generata un'eccezione OverlappingFileLockException. Vedere Javadoc per un elenco completo delle possibili eccezioni.

Possiamo anche eseguire un blocco non bloccante usando channel.tryLock () . Se non riesce a ottenere un blocco perché un altro programma ne contiene uno sovrapposto, restituisce null . Se non riesce a farlo per qualsiasi altro motivo, viene generata un'eccezione appropriata.

4.2. Blocchi esclusivi che utilizzano un RandomAccessFile

Con un RandomAccessFile , dobbiamo impostare i flag sul secondo parametro del costruttore.

Qui, apriremo il file con i permessi di lettura e scrittura:

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

Se apriamo il file in modalità di sola lettura e proviamo a scrivere sul suo canale, verrà generata un'eccezione NonWritableChannelException .

4.3. I blocchi esclusivi richiedono un canale file scrivibile

Come accennato in precedenza, i blocchi esclusivi richiedono un canale scrivibile. Pertanto, non possiamo ottenere un blocco esclusivo tramite un FileChannel creato da un FileInputStream :

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 }

Nell'esempio sopra, il metodo lock () genererà un'eccezione NonWritableChannelException . In effetti, questo è perché stiamo invocando getChannel su un FileInputStream , che crea un canale di sola lettura.

Questo esempio è solo per dimostrare che non possiamo scrivere su un canale non scrivibile. In uno scenario reale, non avremmo catturato e rilanciato l'eccezione.

5. Blocchi condivisi

Ricorda, i blocchi condivisi sono anche chiamati blocchi di lettura . Quindi, per ottenere un blocco di lettura, dobbiamo usare un FileChannel leggibile .

Tale FileChannel può essere ottenuto chiamando il metodo getChannel () su FileInputStream o RandomAccessFile . Ancora una volta, un'altra opzione è utilizzare il metodo di apertura statica della classe FileChannel . In tal caso, impostiamo il secondo argomento su StandardOpenOption.READ :

try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ); FileLock lock = channel.lock(0, Long.MAX_VALUE, true)) { // read from the channel }

One thing to note here is that we chose to lock the entire file by calling lock(0, Long.MAX_VALUE, true). We could also have locked only a specific region of the file by changing the first two parameters to different values. The third parameter has to be set to true in the case of a shared lock.

To keep things simple, we’ll be locking the whole file in all the examples below, but keep in mind we can always lock a specific region of a file.

5.1. Shared Locks Using a FileInputStream

A FileChannel obtained from a FileInputStream is readable. We can, therefore, get a shared lock:

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 }

In the snippet above, the call to lock() on the channel will succeed. That's because a shared lock only requires that the channel is readable. It's the case here since we created it from a FileInputStream.

5.2. Shared Locks Using a RandomAccessFile

This time, we can open the file with just read permissions:

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 }

In this example, we created a RandomAccessFile with read permissions. We can create a readable channel from it and, thus, create a shared lock.

5.3. Shared Locks Require a Readable FileChannel

For that reason, we can't acquire a shared lock through a FileChannel created from a FileOutputStream:

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 } 

In this example, the call to lock() tries to get a shared lock on a channel created from a FileOutputStream. Such a channel is write-only. It doesn't fulfil the need that the channel has to be readable. This will trigger a NonWritableChannelException.

Again, this snippet is just to demonstrate that we can't read from a non-readable channel.

6. Things to Consider

In practice, using file locks is difficult; the locking mechanisms aren’t portable. We’ll need to craft our locking logic with this in mind.

In POSIX systems, locks are advisory. Different processes reading or writing to a given file must agree on a locking protocol. This will ensure the file’s integrity. The OS itself won’t enforce any locking.

Su Windows, i blocchi saranno esclusivi a meno che non sia consentita la condivisione. Discutere i vantaggi o gli svantaggi dei meccanismi specifici del sistema operativo esula dallo scopo di questo articolo. Tuttavia, è importante conoscere queste sfumature quando si implementa un meccanismo di blocco.

7. Conclusione

In questo tutorial, abbiamo esaminato diverse opzioni per ottenere blocchi di file in Java.

Innanzitutto, siamo partiti dalla comprensione dei due principali meccanismi di blocco e del modo in cui la libreria Java NIO facilita il blocco dei file. Quindi abbiamo esaminato una serie di semplici esempi che mostrano che possiamo ottenere blocchi esclusivi e condivisi nelle nostre applicazioni. Abbiamo anche esaminato i tipi di eccezioni tipiche che potremmo incontrare lavorando con i blocchi dei file.

Come sempre, il codice sorgente per gli esempi è disponibile su GitHub.