欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

深入理解Java NIO中的管道(Pipe)与文件锁定(FileLock)机制

最编程 2024-07-28 18:08:16
...

Pipe


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


image.png


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();
    }


推荐阅读