New version of the article

This commit is contained in:
Philippe Soares 2020-01-12 17:26:59 -05:00
parent af27178965
commit 9e93614755
6 changed files with 289 additions and 10120 deletions

View File

@ -8,4 +8,5 @@ This module contains articles about core Java non-blocking input and output (IO)
- [Create a Symbolic Link with Java](https://www.baeldung.com/java-symlink) - [Create a Symbolic Link with Java](https://www.baeldung.com/java-symlink)
- [Introduction to the Java NIO Selector](https://www.baeldung.com/java-nio-selector) - [Introduction to the Java NIO Selector](https://www.baeldung.com/java-nio-selector)
- [Using Java MappedByteBuffer](https://www.baeldung.com/java-mapped-byte-buffer) - [Using Java MappedByteBuffer](https://www.baeldung.com/java-mapped-byte-buffer)
- [How to Lock a File in Java](https://www.baeldung.com/how-to-lock-a-file-in-java)
- [[<-- Prev]](/core-java-modules/core-java-nio) - [[<-- Prev]](/core-java-modules/core-java-nio)

File diff suppressed because it is too large Load Diff

View File

@ -14,22 +14,5 @@
<version>0.0.1-SNAPSHOT</version> <version>0.0.1-SNAPSHOT</version>
<relativePath>../../parent-java</relativePath> <relativePath>../../parent-java</relativePath>
</parent> </parent>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.github.jnr/jnr-ffi -->
<dependency>
<groupId>com.github.jnr</groupId>
<artifactId>jnr-ffi</artifactId>
<version>2.1.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.jnr/jnr-constants -->
<dependency>
<groupId>com.github.jnr</groupId>
<artifactId>jnr-constants</artifactId>
<version>0.9.14</version>
</dependency>
</dependencies>
</project> </project>

View File

@ -1,105 +1,233 @@
package com.baeldung.lock; package com.baeldung.lock;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.channels.FileLock; import java.nio.channels.FileLock;
import java.nio.charset.StandardCharsets; import java.nio.channels.NonReadableChannelException;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.OverlappingFileLockException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
import jnr.ffi.LibraryLoader; import com.google.common.base.Charsets;
import jnr.ffi.Memory;
import jnr.ffi.Pointer;
import jnr.ffi.types.pid_t;
public class FileLocks { public class FileLocks {
public static interface LibC { // Write locks
/**
public static final int O_NONBLOCK = jnr.constants.platform.OpenFlags.O_NONBLOCK.intValue(); * Trying to get an exclusive lock on a read-only FileChannel won't work.
public static final int O_RDWR = jnr.constants.platform.OpenFlags.O_RDWR.intValue(); */
public static final int O_EXLOCK = jnr.constants.platform.OpenFlags.O_EXLOCK.intValue(); static void getExclusiveLockFromInputStream() throws IOException, NonWritableChannelException {
Path path = Files.createTempFile("foo", "txt");
public long write(int fd, Pointer data, long len); try (FileInputStream fis = new FileInputStream(path.toFile()); FileLock lock = fis.getChannel().lock()) {
System.out.println("This won't happen");
@pid_t } catch (NonWritableChannelException e) {
long getpid(); System.err.println(
"The channel obtained through a FileInputStream isn't writable. "
int open(String filename, int flags); + "You can't obtain an exclusive lock on it!");
throw e;
int close(int fd); }
} }
public static void main(String[] args) throws IOException, InterruptedException {
/**
Path path = Paths.get("/tmp/foo"); * Getting an exclusive lock from a RandomAccessFile works if the file is in write mode.
* @param from beginning of the locked region
// Delete the file if it exists * @param size how many bytes to lock
Files.deleteIfExists(path); * @return
* @throws IOException
// Start with a fresh empty file */
Files.createFile(path); static FileLock getExclusiveLockFromRandomAccessFile(long from, long size) throws IOException {
Path path = Files.createTempFile("foo", "txt");
// Prepare some external libc calls. Will only work on systems that have libc. try (RandomAccessFile file = new RandomAccessFile(path.toFile(), "rw");
LibC libc = LibraryLoader.create(LibC.class).load("c"); FileLock lock = file.getChannel().lock(from, size, false)) {
byte[] bytes = "Hello from C\n".getBytes("UTF-8"); if (lock.isValid()) {
jnr.ffi.Runtime runtime = jnr.ffi.Runtime.getRuntime(libc); System.out.println("This is a valid exclusive lock");
Pointer buffer = Memory.allocateDirect(runtime, bytes.length); return lock;
buffer.put(0, bytes, 0, bytes.length); }
return null;
// Open the file through a libc call. This returns a file descriptor. } catch (Exception e) {
int fd = libc.open(path.toString(), libc.O_RDWR + libc.O_EXLOCK + libc.O_NONBLOCK); System.out.println(e.getMessage());
System.out.println("Opened the file through a libc call that locks it."); }
return null;
// Our java method will see the lock. Itd will be well behaved and won't write to the file.
// Note that external processes on POSIX systems would still be able to write to this file ignoring any locks.
writeToRandomAccessFile(path, "I won't write this", 0L);
// Libc opened the file, it can write to its corresponding file handle.
libc.write(fd, buffer, bytes.length);
// Now let's close the file through a libc call, to release its lock.
libc.close(fd);
System.out.println("Invoked libc's close() method");
// This time our method won't see the lock and will write to the file.
writeToRandomAccessFile(path, "Hello from java", bytes.length);
System.out.println("Now that all the locks are gone, here are the file contents:");
System.out.println("------------------------------------------------------------");
Files.lines(path).forEach(System.out::println);
} }
public static RandomAccessFile writeToRandomAccessFile(Path path, String data, long position) { /**
RandomAccessFile file = null; * Getting a write lock on a file region
try { */
file = new RandomAccessFile(path.toFile(), "rws"); static FileLock getExclusiveLockFromFileChannelOpen(long from, long size) throws IOException {
FileChannel channel = file.getChannel(); Path path = Files.createTempFile("foo", "txt");
// Try to acquire a lock try (FileChannel channel = FileChannel.open(path, StandardOpenOption.APPEND);
try (FileLock lock = channel.tryLock()) { FileLock lock = channel.lock(from, size, false)) {
if (lock == null) { String text = "Hello, world.";
System.out.println("Tried to lock through the FileChannel's lock() method. This file is already locked! It's my responsibility to not write to it, even if the OS would let me!"); ByteBuffer buffer = ByteBuffer.allocate(text.length() + System.lineSeparator().length());
} else { buffer.put((text + System.lineSeparator()).getBytes(Charsets.UTF_8));
System.out.println("I don't see a lock on this file anymore. Now I can write to it."); buffer.flip();
int i = 0; while (buffer.hasRemaining()) {
channel.write( channel.write(buffer, channel.size());
ByteBuffer.wrap((data).getBytes(StandardCharsets.UTF_8)), position); }
} System.out.println("This was written to the file");
} catch (Exception e) { Files.lines(path).forEach(System.out::println);
System.out.println("Error while locking"); return lock;
}
}
// Read locks
/**
* Trying to get a shared lock on a write-only FileChannel won't work.
*/
static void getReadLockFromOutputStream(long from, long size) throws IOException {
Path path = Files.createTempFile("foo", "txt");
try (FileOutputStream fis = new FileOutputStream(path.toFile());
FileLock lock = fis.getChannel().lock(0, Long.MAX_VALUE, true)) {
System.out.println("This won't happen");
} catch (NonReadableChannelException e) {
System.err.println(
"The channel obtained through a FileOutputStream isn't readable. "
+ "You can't obtain an shared lock on it!");
throw e;
}
}
/**
* Locking a file for reading doesn't require a writable FileChannel.
*
* @param from beginning of the locked region
* @param size how many bytes to lock
* @return
* @throws IOException
*/
static FileLock getReadLockFromInputStream(long from, long size) throws IOException {
Path path = Files.createTempFile("foo", "txt");
try (FileInputStream fis = new FileInputStream(path.toFile());
FileLock lock = fis.getChannel().lock(from, size, true)) {
if (lock.isValid()) {
System.out.println("This is a valid shared lock");
return lock;
}
return null;
}
}
/**
* Getting an exclusive lock from a RandomAccessFile works if the file is in read mode.
* @param from beginning of the locked region
* @param size how many bytes to lock
* @return
* @throws IOException
*/
static FileLock getReadLockFromRandomAccessFile(long from, long size) throws IOException {
Path path = Files.createTempFile("foo", "txt");
try (RandomAccessFile file = new RandomAccessFile(path.toFile(), "r"); // could also be "rw", but "r" is sufficient for reading
FileLock lock = file.getChannel().lock(from, size, true)) {
if (lock.isValid()) {
System.out.println("This is a valid shared lock");
return lock;
}
return null;
} catch (Exception e) {
System.out.println(e.getMessage());
}
return null;
}
static class Writer implements Runnable {
private Path path;
private volatile RandomAccessFile file;
private String text;
private volatile CountDownLatch countDownLatch;
/**
*
* @param path The path to the file we will write into
* @param text The text to write
* @param countDownLatch A counter for thread synchronization
*
*/
public Writer(Path path, String text, CountDownLatch countDownLatch) {
this.path = path;
this.text = text;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
lockAndWrite();
} catch (InterruptedException | FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
} catch (Exception e) { countDownLatch.countDown();
System.out.println("Other Error.");
e.printStackTrace();
} }
return file;
}
private void lockAndWrite() throws InterruptedException, FileNotFoundException {
ByteBuffer buffer = null;
if (file == null) {
file = new RandomAccessFile(path.toFile(), "rw");
}
try (FileChannel channel = file.getChannel()) {
try (FileLock lock = channel.tryLock(channel.size(),
channel.size() + text.length() + System.lineSeparator().length(), true)) {
if (lock != null) {
String text = "Hello, world.";
buffer = ByteBuffer.allocate(text.length() + System.lineSeparator().length());
buffer.put((text + System.lineSeparator()).getBytes(Charsets.UTF_8));
buffer.flip();
while (buffer.hasRemaining()) {
channel.write(buffer, channel.size());
}
}
} catch (OverlappingFileLockException e) {
// Failed to lock. Try again later.
Thread.sleep(50);
lockAndWrite();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException, IOException {
Path path = Paths.get("/tmp/foo");
Files.deleteIfExists(path);
Files.createFile(path);
int concurrentWriters = 5;
CountDownLatch countDownLatch = new CountDownLatch(concurrentWriters);
// Launch 10 writers in parallel
final AtomicInteger count = new AtomicInteger(0);
Stream.generate(() -> new Thread(new Writer(path, "foo " + count.incrementAndGet(), countDownLatch)))
.limit(concurrentWriters).forEach(Thread::start);
countDownLatch.await();
AtomicInteger lineCount = new AtomicInteger(0);
Files.lines(path).forEach((line) -> {
lineCount.incrementAndGet();
System.out.println(line);
});
System.out.println("Total lines written = " + lineCount.get());
}
} }

View File

@ -1,21 +0,0 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_baeldung_lock_FileLocks */
#ifndef _Included_com_baeldung_lock_FileLocks
#define _Included_com_baeldung_lock_FileLocks
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_baeldung_lock_FileLocks
* Method: getpid
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_com_baeldung_lock_FileLocks_getpid
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,78 @@
package com.baeldung.lock;
import static org.junit.Assert.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.nio.channels.FileLock;
import java.nio.channels.NonReadableChannelException;
import java.nio.channels.NonWritableChannelException;
import org.junit.jupiter.api.Test;
class FileLocksTest {
// Exclusive locks
/**
* Getting an exclusive (write) lock on the entire file.
* Fails when getting the lock from a FileChannel obtained through a FileInputStream.
*/
@Test
void givenAnInputStream_whenGetWriteLock_throwNonWritableChannelException() {
assertThrows(NonWritableChannelException.class,
() -> FileLocks.getExclusiveLockFromInputStream());
}
/**
* Getting and exclusive lock from a RandomAccessFile
* @throws IOException
*/
@Test
void givenARandomAccessFileWithReadWriteAccess_whenGetWriteLock_lock() throws IOException {
FileLock lock = FileLocks.getExclusiveLockFromRandomAccessFile(0, 10);
assertNotNull(lock);
assertFalse(lock.isShared());
}
/**
* Getting an exclusive lock from FileChannel::open
* @throws IOException
*/
@Test
void givenAPath_whenGetExclusiveLock_lock() throws IOException {
FileLock lock = FileLocks.getExclusiveLockFromFileChannelOpen(0, 10);
assertNotNull(lock);
assertFalse(lock.isShared());
}
/**
* Getting a shared (read) lock works fine when getting the lock from a FileChannel obtained through a FileInputStream.
* @throws IOException
*/
@Test
void givenAnInputStream_whenGetSharedLock_lock() throws IOException {
FileLock lock = FileLocks.getReadLockFromInputStream(0, 10);
assertNotNull(lock);
assertTrue(lock.isShared());
}
@Test
void givenAFileOutputStream_whenGetSharedLock_throwNonReadableChannelException() {
assertThrows(NonReadableChannelException.class,
() -> FileLocks.getReadLockFromOutputStream(0, 10));
}
@Test
void givenARandomAccessFile_whenGetSharedLock_lock() throws IOException {
FileLock lock = FileLocks.getReadLockFromRandomAccessFile(0, 10);
assertNotNull(lock);
assertTrue(lock.isShared());
}
}