Cara Mengunci File di Java

1. Ikhtisar

Saat membaca atau menulis file, kita perlu memastikan mekanisme penguncian file yang tepat tersedia. Hal ini memastikan integritas data dalam aplikasi berbasis I / O secara bersamaan.

Dalam tutorial ini, kita akan melihat berbagai pendekatan untuk mencapai ini menggunakan pustaka Java NIO .

2. Pengenalan File Locks

Secara umum, ada dua jenis gembok :

    • Kunci eksklusif - juga dikenal sebagai kunci tulis
    • Kunci bersama - juga disebut sebagai kunci baca

Sederhananya, kunci eksklusif mencegah semua operasi lain - termasuk membaca - sementara operasi tulis selesai.

Sebaliknya, kunci bersama memungkinkan lebih dari satu proses untuk membaca pada waktu yang sama. Inti dari kunci baca adalah untuk mencegah perolehan kunci tulis oleh proses lain. Biasanya, file dalam keadaan yang konsisten seharusnya dapat dibaca oleh proses apa pun.

Di bagian selanjutnya, kita akan melihat bagaimana Java menangani jenis kunci ini.

3. Kunci File di Java

Perpustakaan Java NIO memungkinkan penguncian file pada tingkat OS. Metode lock () dan tryLock () dari FileChannel adalah untuk tujuan itu.

Kita dapat membuat FileChannel melalui FileInputStream , FileOutputStream , atau RandomAccessFile . Ketiganya memiliki metode getChannel () yang mengembalikan FileChannel .

Alternatifnya, kita dapat membuat FileChannel secara langsung melalui metode buka statis :

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

Selanjutnya, kami akan meninjau berbagai opsi untuk mendapatkan kunci eksklusif dan bersama di Java. Untuk mempelajari lebih lanjut tentang saluran file, lihat tutorial Panduan untuk Java FileChannel kami.

4. Kunci Eksklusif

Seperti yang telah kita pelajari, saat menulis ke file, kita dapat mencegah proses lain membaca atau menulisnya dengan menggunakan kunci eksklusif .

Kami mendapatkan kunci eksklusif dengan memanggil lock () atau tryLock () pada kelas FileChannel . Kami juga dapat menggunakan metode kelebihan beban mereka:

  • kunci (posisi panjang, ukuran panjang, boolean bersama)
  • tryLock (posisi panjang, ukuran panjang, boolean bersama)

Dalam kasus tersebut, parameter bersama harus disetel ke false .

Untuk mendapatkan kunci eksklusif, kita harus menggunakan FileChannel yang dapat ditulis . Kita bisa membuatnya melalui metode getChannel () dari FileOutputStream atau RandomAccessFile . Alternatifnya, seperti yang disebutkan sebelumnya, kita dapat menggunakan metode buka statis dari kelas FileChannel . Yang kita butuhkan hanyalah menyetel argumen kedua ke StandardOpenOption.APPEND :

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

4.1. Kunci Eksklusif Menggunakan FileOutputStream

Sebuah FileChannel dibuat dari FileOutputStream dapat ditulis. Karena itu, kami dapat memperoleh kunci eksklusif:

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

Di sini, channel.lock () akan memblokir hingga mendapatkan kunci, atau akan memunculkan pengecualian. Misalnya, jika wilayah yang ditentukan sudah dikunci, OverlappingFileLockException akan muncul. Lihat Javadoc untuk daftar lengkap kemungkinan pengecualian.

Kami juga dapat melakukan kunci non-pemblokiran menggunakan channel.tryLock () . Jika gagal mendapatkan kunci karena program lain memegang yang tumpang tindih, maka ia mengembalikan null . Jika gagal melakukannya karena alasan lain, maka pengecualian yang sesuai akan dilemparkan.

4.2. Kunci Eksklusif Menggunakan RandomAccessFile

Dengan RandomAccessFile , kita perlu mengatur flag pada parameter kedua dari konstruktor.

Di sini, kami akan membuka file dengan izin baca dan tulis:

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

Jika kita membuka file dalam mode hanya-baca dan mencoba menulis ke salurannya, itu akan memunculkan NonWritableChannelException .

4.3. Kunci Eksklusif Membutuhkan Saluran File yang Dapat Ditulisi

Seperti yang disebutkan sebelumnya, kunci eksklusif membutuhkan saluran yang dapat ditulis. Oleh karena itu, kami tidak bisa mendapatkan kunci eksklusif melalui FileChannel yang dibuat dari 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 }

Dalam contoh di atas, metode lock () akan memunculkan NonWritableChannelException . Memang, ini karena kami menjalankan getChannel di FileInputStream , yang membuat saluran hanya-baca.

Contoh ini hanya untuk menunjukkan bahwa kita tidak dapat menulis ke saluran yang tidak dapat ditulis. Dalam skenario dunia nyata, kami tidak akan menangkap dan memunculkan kembali pengecualian.

5. Kunci Bersama

Ingat, kunci bersama juga disebut kunci baca . Oleh karena itu, untuk mendapatkan kunci baca, kita harus menggunakan FileChannel yang dapat dibaca .

FileChannel semacam itu bisa diperoleh dengan memanggil metode getChannel () pada FileInputStream atau RandomAccessFile . Sekali lagi, opsi lain adalah menggunakan metode buka statis dari kelas FileChannel . Dalam hal ini, kami menetapkan argumen kedua ke StandardOpenOption.READ :

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

Satu hal yang perlu diperhatikan di sini adalah kami memilih untuk mengunci seluruh file dengan memanggil kunci (0, Long.MAX_VALUE, true) . Kami juga dapat mengunci hanya wilayah tertentu dari file tersebut dengan mengubah dua parameter pertama ke nilai yang berbeda. Parameter ketiga harus disetel ke true dalam kasus kunci bersama.

Untuk mempermudah, kami akan mengunci seluruh file dalam semua contoh di bawah ini, tetapi perlu diingat bahwa kami selalu dapat mengunci wilayah file tertentu.

5.1. Kunci Bersama Menggunakan FileInputStream

Sebuah FileChannel diperoleh dari FileInputStream dibaca. Karena itu, kami dapat memperoleh kunci bersama:

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 }

Dalam potongan di atas, panggilan ke lock () pada saluran akan berhasil. Itu karena kunci bersama hanya mengharuskan saluran itu dapat dibaca. Ini terjadi di sini sejak kami membuatnya dari FileInputStream .

5.2. Kunci Bersama Menggunakan RandomAccessFile

Kali ini, kami dapat membuka file hanya dengan izin baca :

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 }

Dalam contoh ini, kami membuat RandomAccessFile dengan izin baca. Kita dapat membuat saluran yang dapat dibaca darinya dan, dengan demikian, membuat kunci bersama.

5.3. Kunci Bersama Membutuhkan Saluran File yang Dapat Dibaca

Oleh karena itu, kami tidak dapat memperoleh kunci bersama melalui FileChannel yang dibuat dari 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.

Di Windows, kunci akan menjadi eksklusif kecuali berbagi diperbolehkan. Membahas keuntungan atau kerugian dari mekanisme khusus OS berada di luar cakupan artikel ini. Namun, penting untuk mengetahui nuansa ini saat menerapkan mekanisme penguncian.

7. Kesimpulan

Dalam tutorial ini, kami telah meninjau beberapa opsi berbeda untuk mendapatkan kunci file di Java.

Pertama, kami mulai dengan memahami dua mekanisme penguncian utama dan bagaimana pustaka Java NIO memfasilitasi penguncian file. Kemudian kami menelusuri serangkaian contoh sederhana yang menunjukkan bahwa kami dapat memperoleh kunci eksklusif dan bersama dalam aplikasi kami. Kami juga melihat jenis pengecualian umum yang mungkin kami temui saat bekerja dengan kunci file.

Seperti biasa, kode sumber untuk contoh tersedia di GitHub.