Java NIO 中的 Pipe 和 FileLock 详解

1,273 阅读4分钟

「这是我参与2022首次更文挑战的第16天,活动详情查看:2022首次更文挑战」。

Pipe

Java NIO 管道是 2 个线程之间的单向数据连接, Pipe 有一个 source 通道和一个 sink 通道。数据会被下入到 Sink 通道,从 source 通道读取。

1、创建通道

通过 Pipe.open() 方法打开管道

Pipe pipe = Pipe.open();

2、写入通道

要向管道写入数据,需要访问 sink 通道。

Pipe.Sinkchannel sinkChannel = pipe.sink();

通过调用 Sinkchannel 的 write() 方法,将数据写入 SinkChannel :

String newData = "New String to write to file .." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.alloacte(48);

buf.clear();
buf.put(newData.getBytes());
buf.flip();

while(buf.hasRemaining()) {
	sinkChannel.write(buf);
}

3、从管道读取数据

从读取管道的数据,需要访问 source 通道,像这样:

Pipe.SourceChannel sourceChannel = pipe.source();

调用 source 通道的 read() 方法来读取数据:

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = sourceChannel.read(buf);

read() 方法返回的 int 值会告诉我们多少字节被读进了缓冲区。

4、示例

我们将通过Pipe,Sink进行数据的读写操作, 示例如下:

public class PipeDemo {

    public static void main(String[] args) throws IOException {
        // 1 获取管道
        Pipe pipe = Pipe.open();

        // 2 获取 sink 通道
        Pipe.SinkChannel sinkChannel = pipe.sink();

        // 3 创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put("ha ha!".getBytes(StandardCharsets.UTF_8));
        buffer.flip();

        // 4 写入数据
        sinkChannel.write(buffer);

        // 5 source 通道
        Pipe.SourceChannel sourceChannel = pipe.source();

        // 6 创建一个缓冲区,读取数据
        ByteBuffer buffer2 = ByteBuffer.allocate(1024);
        //buffer2.flip();
        int len = sourceChannel.read(buffer2);
        String str = new String(buffer2.array(), 0, len);
        System.out.println(str);

        // 7 关闭通道
        sinkChannel.close();
        sourceChannel.close();
    }
}

FileLock

FileLock 简介

文件锁在 OS 中很常见,如果多个程序同时访问、修改同一个文件,很容易英文文件数据不同步而出现问题。给文件加一个锁,同一事件,只能有一个程序修改此文件,或者程序都只能读此文件,这就解决了同步问题。

文件锁是进程级别的,不是线程级别的,文件锁可以解决多个线程并发的访问、修改同一个文件的问题,但是不能解决多线程并发访问,修改同一个文件的问题。

使用文件锁时,同一个进程内的多个线程,可以同时访问,修改此文件。

文件锁是当前程序锁属的 JVM 实例持有的,一旦获取到文件锁(对文件加锁),要调用 release() 或者关闭对应 FileChannel 对象,或者当前 JVM 退出才会释放这个锁。

一旦某个进程(比如 JVM 实例)对某个文件加锁,则在释放这个锁之前,此进程不不能在对此文件加锁,就是 JVM 实例在同一个问加上的文件锁是不能重叠的(进程级别不能重复在同一文件上获取锁)。

2、文件锁分配

排他锁: 又叫独占锁,对文件加排他锁后,该进程可以对此文件进行读写,该进程独占此文件,其他进程不能读写此文件,知道该文件释放文件锁。

共享锁: 某个进程对文件加共享锁,其他进程也可以访问此文件,但是这些进程都只能读此文件,不能写。线程是安全的。只要还有一个进程持有共享锁,此文件只能读,不能写。(其实只能共享读,不能共享写)。

3、使用示例

// 创建 FileChannel 对象,文件锁只能通过 FileChannel 对象来使用
String pathName = "/xxx/01.txt";
FileChannel fileChannel = new FileOutputStream(pathName).getChannel();

// 对文件加锁
FileLock lock = fileChannel.lock();

// 对此文件进行读写操作
// ....

// 释放锁
lock.release();

文件锁要通过 FileChannel 对象使用。

4、获取文件锁方法

有 4 中获取文件锁的方法:

  • lock() // 对整个文件加锁,默认为排他锁

  • lock(long position, long size, boolean shared) // 自定义加锁方式。前面两个参数指定要加锁的部分(可以只对此文件的部分内容加锁),第三个参数值指定是否共享锁。

  • trylock() // 对整个文件加锁,默认为排他锁。

  • trylock(long position, long size, boolean shared) // 自定义加锁方式。如果指定为共享锁,则其他进程可以读此文件,所有进程均不能写此文件,如果某进程视图对此文件进行写操作,会抛出异常。

5、lock 与 trylock 区别

lock 是阻塞式的,如果未获取到文件锁,会一直阻塞当前线程,知道获取文件锁。

trylocklock 的作用相同,只不过 trylock 是非阻塞式的,trylock 是尝试获取文件锁,获取成功后就返回锁对象,否则返回 null, 不会阻塞当前线程。

6、fileLock 两个方法

boolean isShared() // 此文件锁是否共享锁

boolean isValid() // 此文件锁是否还有效果

在某些 OS 上,对某个文件加锁后,不能对此文件使用通道映射。

7、完整示例

public class FileLockDemo2 {


    public static void main(String[] args) throws IOException {
        String input = "hello!";
        System.out.println(input);


        ByteBuffer buffer = ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8));
        String pathName = "/xxx/01.txt";
        Path path = Paths.get(pathName);

        FileChannel fileChannel = FileChannel.open(path,
                                                   StandardOpenOption.WRITE, StandardOpenOption.APPEND);
        fileChannel.position(fileChannel.size() - 1);

        // 加锁
        FileLock lock = fileChannel.lock();
		// 如果是共享锁则不能执行后续的 write 操作
        //FileLock lock = fileChannel.lock(0, Long.MAX_VALUE, true);
        
        System.out.println("isShared: " + lock.isShared());

        fileChannel.write(buffer);
        fileChannel.close();

        readFile(pathName);
    }

    private static void readFile(String pathName) throws IOException {
        FileReader fileReader = new FileReader(pathName);
        BufferedReader bufferedReader = new BufferedReader(fileReader);
        String str = bufferedReader.readLine();
        System.out.println("读取内容:");
        while (str != null) {
            System.out.println("  " + str);
            str = bufferedReader.readLine();
        }
        fileReader.close();
        bufferedReader.close();
    }

}