Hoe een bestand in Java te vergrendelen

1. Overzicht

Bij het lezen of schrijven van bestanden moeten we ervoor zorgen dat de juiste mechanismen voor bestandsvergrendeling aanwezig zijn. Dit zorgt voor gegevensintegriteit in gelijktijdige op I / O gebaseerde toepassingen.

In deze zelfstudie bekijken we verschillende benaderingen om dit te bereiken met behulp van de Java NIO-bibliotheek.

2. Inleiding tot bestandsvergrendelingen

Over het algemeen zijn er twee soorten sloten:

    • Exclusieve sloten - ook wel schrijfvergrendelingen genoemd
    • Gedeelde vergrendelingen - ook wel leesvergrendelingen genoemd

Simpel gezegd, een exclusieve vergrendeling voorkomt alle andere bewerkingen - inclusief lezen - terwijl een schrijfbewerking wordt voltooid.

Bij een gedeeld slot daarentegen kunnen meer dan één proces tegelijkertijd worden gelezen. Het doel van een leesvergrendeling is om te voorkomen dat een schrijfvergrendeling door een ander proces wordt verkregen. Typisch zou een bestand in een consistente staat inderdaad leesbaar moeten zijn door elk proces.

In de volgende sectie zullen we zien hoe Java met dit soort vergrendelingen omgaat.

3. Bestandsvergrendelingen in Java

De Java NIO-bibliotheek maakt het vergrendelen van bestanden op OS-niveau mogelijk. De slot() en tryLock () methoden van een FileChannel zijn voor dat doel.

We kunnen een FileChannel via een FileInputStream, een FileOutputStream, of een RandomAccessFile. Alle drie hebben een getChannel () methode die een FileChannel.

Als alternatief kunnen we een FileChannel direct via de statische Open methode:

probeer (FileChannel-kanaal = FileChannel.open (pad, openOptions)) {// schrijf naar het kanaal}

Vervolgens zullen we verschillende opties bekijken om exclusieve en gedeelde vergrendelingen in Java te krijgen. Raadpleeg onze handleiding voor Java FileChannel-zelfstudie voor meer informatie over bestandskanalen.

4. Exclusieve sloten

Zoals we al hebben geleerd, tijdens het schrijven naar een bestand, we kunnen voorkomen dat andere processen het lezen of ernaar schrijven door een exclusief slot te gebruiken.

We krijgen exclusieve sloten door te bellen slot() of tryLock () op de FileChannel klasse. We kunnen ook hun overbelaste methoden gebruiken:

  • lock (lange positie, lange grootte, boolean gedeeld)
  • tryLock (lange positie, lange grootte, boolean gedeeld)

In die gevallen is het gedeeld parameter moet worden ingesteld op false.

Om een ​​exclusief slot te krijgen, moeten we een beschrijfbaar FileChannel. We kunnen het maken via de getChannel () methoden van een FileOutputStream of een RandomAccessFile. Als alternatief kunnen we, zoals eerder vermeld, de static gebruiken Open methode van de FileChannel klasse. Het enige wat we nodig hebben is om het tweede argument in te stellen op StandardOpenOption.APPEND:

probeer (FileChannel-kanaal = FileChannel.open (pad, StandardOpenOption.APPEND)) {// schrijf naar kanaal}

4.1. Exclusieve sloten met een FileOutputStream

EEN FileChannel gemaakt op basis van een FileOutputStream is beschrijfbaar. We kunnen daarom een ​​exclusief slot aanschaffen:

probeer (FileOutputStream fileOutputStream = nieuwe FileOutputStream ("/ tmp / testfile.txt"); FileChannel channel = fileOutputStream.getChannel (); FileLock lock = channel.lock ()) {// schrijf naar het kanaal}

Hier, channel.lock () zal ofwel blokkeren totdat het een vergrendeling krijgt, of het zal een uitzondering genereren. Als de opgegeven regio bijvoorbeeld al is vergrendeld, wordt een OverlappingFileLockException wordt gegooid. Zie Javadoc voor een volledige lijst met mogelijke uitzonderingen.

We kunnen ook een niet-blokkerende vergrendeling uitvoeren met kanaal.tryLock (). Als het geen vergrendeling krijgt omdat een ander programma een overlappend programma bevat, keert het terug nul. Als dit om een ​​andere reden niet lukt, wordt een passende uitzondering gegenereerd.

4.2. Exclusieve sloten met een RandomAccessFile

Met een RandomAccessFile, we moeten vlaggen instellen op de tweede parameter van de constructor.

Hier gaan we het bestand openen met lees- en schrijfrechten:

probeer (RandomAccessFile file = nieuwe RandomAccessFile ("/ tmp / testfile.txt", "rw"); FileChannel channel = file.getChannel (); FileLock lock = channel.lock ()) {// schrijf naar het kanaal} 

Als we het bestand openen in de modus alleen-lezen en proberen naar het kanaal te schrijven, wordt een NonWritableChannelException.

4.3. Exclusieve sloten vereisen een schrijfbaar FileChannel

Zoals eerder vermeld, hebben exclusieve vergrendelingen een beschrijfbaar kanaal nodig. Daarom kunnen we geen exclusief slot krijgen via een FileChannel gemaakt op basis van een FileInputStream:

Padpad = Files.createTempFile ("foo", "txt"); Logger log = LoggerFactory.getLogger (this.getClass ()); probeer (FileInputStream fis = nieuwe FileInputStream (path.toFile ()); FileLock lock = fis.getChannel (). lock ()) {// onbereikbare code} catch (NonWritableChannelException e) {// behandel uitzondering}

In het bovenstaande voorbeeld is de slot() methode zal een NonWritableChannelException. Dit komt inderdaad omdat we een beroep doen getChannel op een FileInputStream, waarmee een alleen-lezen kanaal wordt gemaakt.

Dit voorbeeld is alleen om aan te tonen dat we niet naar een niet-schrijfbaar kanaal kunnen schrijven. In een realistisch scenario zouden we de uitzondering niet vangen en opnieuw gooien.

5. Gedeelde sloten

Onthoud dat gedeelde sloten ook wel worden genoemd lezen sloten. Om een ​​leesvergrendeling te krijgen, moeten we daarom een ​​leesbaar FileChannel.

Zo'n FileChannel kan worden verkregen door te bellen naar de getChannel () methode op een FileInputStream of een RandomAccessFile. Nogmaals, een andere optie is om de static te gebruiken Open methode van de FileChannel klasse. In dat geval stellen we het tweede argument in op StandardOpenOption.READ:

probeer (FileChannel channel = FileChannel.open (pad, StandardOpenOption.READ); FileLock lock = channel.lock (0, Long.MAX_VALUE, true)) {// lees van het kanaal}

Een ding om op te merken is dat we ervoor hebben gekozen om het hele bestand te vergrendelen door te bellen lock (0, Long.MAX_VALUE, true). We hadden ook alleen een specifiek gebied van het bestand kunnen vergrendelen door de eerste twee parameters in verschillende waarden te veranderen. De derde parameter moet worden ingesteld op waar in het geval van een gedeeld slot.

Om het simpel te houden, vergrendelen we het hele bestand in alle onderstaande voorbeelden, maar houd er rekening mee dat we altijd een specifiek gebied van een bestand kunnen vergrendelen.

5.1. Gedeelde sloten met een FileInputStream

EEN FileChannel verkregen van een FileInputStream is leesbaar. We kunnen daarom een ​​gedeeld slot krijgen:

probeer (FileInputStream fileInputStream = nieuwe FileInputStream ("/ tmp / testfile.txt"); FileChannel channel = fileInputStream.getChannel (); FileLock lock = channel.lock (0, Long.MAX_VALUE, true)) {// gelezen van het kanaal }

In het bovenstaande fragment wordt de aanroep naar slot() op het kanaal zal slagen. Dat komt omdat een gedeeld slot alleen vereist dat het kanaal leesbaar is. Dit is hier het geval omdat we het hebben gemaakt op basis van een FileInputStream.

5.2. Gedeelde sloten met een RandomAccessFile

Deze keer kunnen we het bestand openen met slechts lezen rechten:

probeer (RandomAccessFile file = nieuwe RandomAccessFile ("/ tmp / testfile.txt", "r"); FileChannel channel = file.getChannel (); FileLock lock = channel.lock (0, Long.MAX_VALUE, true)) {// gelezen van het kanaal}

In dit voorbeeld hebben we een RandomAccessFile met leesrechten. We kunnen er een leesbaar kanaal van maken en zo een gedeeld slot creëren.

5.3. Gedeelde sloten vereisen een leesbaar FileChannel

Om die reden kunnen we geen gedeeld slot verkrijgen via een FileChannel gemaakt op basis van een FileOutputStream:

Padpad = Files.createTempFile ("foo", "txt"); probeer (FileOutputStream fis = nieuwe FileOutputStream (path.toFile ()); FileLock lock = fis.getChannel (). lock (0, Long.MAX_VALUE, true)) {// onbereikbare code} catch (NonWritableChannelException e) {// handle uitzondering} 

In dit voorbeeld is de aanroep naar slot() probeert een gedeelde vergrendeling te krijgen op een kanaal dat is gemaakt op basis van een FileOutputStream. Zo'n kanaal is alleen-schrijven. Het vervult niet de behoefte dat het kanaal leesbaar moet zijn. Dit zal een NonWritableChannelException.

Nogmaals, dit fragment is alleen om aan te tonen dat we niet kunnen lezen van een niet-leesbaar kanaal.

6. Dingen om te overwegen

In de praktijk is het gebruik van bestandsvergrendelingen moeilijk; de vergrendelingsmechanismen zijn niet draagbaar. Met dit in gedachten moeten we onze vergrendelingslogica ontwikkelen.

In POSIX-systemen zijn sloten adviserend. Verschillende processen die een bepaald bestand lezen of schrijven, moeten het eens zijn over een vergrendelingsprotocol. Dit zorgt voor de integriteit van het bestand. Het besturingssysteem zelf dwingt geen enkele vergrendeling af.

Op Windows zijn vergrendelingen exclusief, tenzij delen is toegestaan. Het bespreken van de voor- en nadelen van OS-specifieke mechanismen valt buiten het bestek van dit artikel. Toch is het belangrijk om deze nuances te kennen bij het implementeren van een vergrendelingsmechanisme.

7. Conclusie

In deze zelfstudie hebben we verschillende opties besproken voor het verkrijgen van bestandsvergrendelingen in Java.

Ten eerste begonnen we met het begrijpen van de twee belangrijkste vergrendelingsmechanismen en hoe de Java NIO-bibliotheek het vergrendelen van bestanden vergemakkelijkt. Vervolgens hebben we een reeks eenvoudige voorbeelden doorlopen die laten zien dat we exclusieve en gedeelde sloten in onze applicaties kunnen krijgen. We hebben ook gekeken naar de soorten typische uitzonderingen die we kunnen tegenkomen bij het werken met bestandsvergrendelingen.

Zoals altijd is de broncode voor de voorbeelden beschikbaar op GitHub.