NIO将数据打包到缓冲区,通道对数据进行传输

通道概述

Channel是连接起始缓冲Buffer区到目的缓冲Buffer区的通道
image.png
Channel的结构较复杂,Channel接口只定义其属性功能,其最终的实现依赖于操作系统平台的实现类似于SPI
在Unix环境下由UnixAsynchronousSocketChannelImpl实现

  • AutoCloseable、Closeable接口
    定义了可以使用try-with-resource结构,自动close,抛出IOException

  • Channel接口
    1.定义了查询通道是否打开
    2.定义了关闭通道

  • NetworkChannel接口
    网络套接字的通道,定义了bind绑定到本地地址

  • AsynchronousChannel接口
    异步 I/O 操作的通道,没有新增任何方法,也没有实现任何方法,标志接口

  • AsynchronousByteChannel
    可读写的异步 I/O 操作的通道,定义了read/wirte

  • AsynchronousSocketChannel抽象类
    和流Buffer挂钩,定义了某些具体的实现,和新增方法规范

  • Cancellable接口
    通过Future取消调用回调

  • Groupable接口
    异步通道组关联的接口

  • AsynchronousSocketChannelImpl抽象类
    AsynchronousSocketChannel基本通用实现

  • UnixAsynchronousServerSocketChannelImpl
    AsynchronousSocketChannelImpl类的Unix实现

  • 读read
    从Channel中读取数据到Buffer缓冲区中

  • 写write
    从Buffer缓冲区的数据写入通道Channel中

AsynchronousChannel定义了异步IO接口

  • close() throws IOException;
    只有一个close方法,当一个通道实现了可异步AsynchronousChannel或者Closebale接口时,close方法可以抛出IOException。或者其子类AsynchronousCloseException、ClosedChannelException

1.Future接口定义了多线程操作,其中cancel方法取消执行会导致等待IO处理的线程抛出异常
2.取消操作离开通道或者离开连接会使得通道状态不一致,会阻止read和wirte。如当前线程取消了读/写操作,但是不能阻止之后的读/写操作
3.调用cancel或取消读/写应废弃I/O操作中所有的缓冲区

AsynchronousByteChannel定义读写接口

支持异步I/O操作读/写Byte字节
一共4个方法:
2个读,2个写
2个Future返回,2个void
image.png
1.是否支持同时进行read/wirte取决于具体实现
2.ByteBuffer类是线程不安全的,多线程情况下应注意

ReadableByteChannel接口阻塞read

    public int read(ByteBuffer dst) throws IOException;

只有一个方法,定义了可以读取字节的规范
1.实现了该read的操作是阻塞的。如果另一个线程已经在这个通道上启动了一个读操作,那么这个方法的调用将被阻塞,直到第一个操作完成
image.png
其实现是加了锁的阻塞

ScatteringByteChannel接口分散读取

public interface ScatteringByteChannel extends ReadableByteChannel

1.是ReadableByteChannel的子接口,其read也是阻塞的
2.定义了2个read方法,可以从通道中分散读取到多个缓冲区Buffer

    public long read(ByteBuffer[] dsts, int offset, int length)
        throws IOException;
    public long read(ByteBuffer[] dsts) throws IOException;

JDK的原文:在实现网络协议或文件格式时,分散读取通常很有用,例如,将数据分组为由一个或多个固定长度的标头和可变长度的主体组成的段

WritableByteChannel阻塞写

和ReadableByteChannel接口类似,用于write,阻塞,加锁,

    public int write(ByteBuffer src) throws IOException;

image.png

GatheringByteChannel写多个Buffer

和ScatteringByteChannel类似。
收集写入操作在单个调用中写入来自一个或多个给定缓冲区序列的字节序列。
在实现网络协议或文件格式时,收集写入通常很有用,例如,将数据分组为由一个或多个固定长度的标头和可变长度的主体组成的段。

ByteChannel接口

extends ReadableByteChannel, WritableByteChannel。接口可以多extends,这个接口只是统一规格没有任何实质性意义。
image.png
有时候觉得JDK这方面弄过头了...

SeekableByteChannel接口

新增4个方法,主要用于维护position,定义了一些规范
image.png

NetworkChannel接口与Socket关联

将Channel与Socket进行关联
套接字的实际工作由SocketImpl类的一个实例SocketImpl 。 应用程序通过更改创建套接字实现的套接字工厂,可以配置自身以创建适合本地防火墙的套接字。

MulticastChannel接口

public interface MulticastChannel
    extends NetworkChannel

支持 Internet 协议 (IP) 多播的网络通道。
多播即将多个主机地址打包,形成一个组,将IP报文想这个组进行发送。
定义了2个新方法

    MembershipKey join(InetAddress group, NetworkInterface interf)
        throws IOException;
    MembershipKey join(InetAddress group, NetworkInterface interf, InetAddress source)
        throws IOException;
  • 吐槽
    感觉JDK这里也很不规范,没有重写,继续是接口,非要写一遍方法,默认public还给省略了,诶。。。
    image.png
    image.png

InterruptibleChannel接口

异步关闭和中断的通道
实现此接口的通道是可异步关闭的:如果一个线程在可中断通道上的 I/O 操作中被阻塞,则另一个线程可能会调用该通道的close方法。这将导致被阻塞的线程接收到AsynchronousCloseException 。
次接口中只有一个close方法抛出IOException

核心实现类

接口定义了规范,其具体实现还是需要类实现的,主要为:

  • AbstractInterruptibleChannel 可中断通道的基本实现类
  • AbstractSelectableChannel 可选通道的基本实现类
  • AsynchronousFileChannel 用于读取、写入和操作文件的异步通道
  • AsynchronousServerSocketChannel 面向流的侦听socket套接字的异步通道
  • AsynchronousSocketChannel 面向流的连接套接字的异步通道
  • DatagramChannel 面向数据报的套接字的可选通道
  • FileChannel 用于读取、写入、映射和操作文件的通道
  • Pipe.SinkChannel 表示Pipe可写端的通道
  • SourceChannel 表示Pipe可读端的通道
  • SelectableChannel 可以通过Selector复用的通道
  • ServerSocketChannel 面向流的侦听socket套接字的可选通道。
  • SocketChannel 面向流的连接socket套接字的可选通道

AbstractInterruptibleChannel抽象类

可中断通道的基本实现类
这个抽象类只具体实现了begin和end(boolean)方法为了实现通道异步关闭和中断

//分别在调用可能无限期阻塞的 I/O 操作之前和之后调用begin和end方法。 为了确保end方法总是被调用,这些方法应该在try ... finally块中使用
boolean completed = false;
   try {
       begin();
       completed = ...;    // Perform blocking I/O operation
       return ...;         // Return result
   } finally {
       end(completed);
   }

其他方法该类均没有实现

FileChannel类

也是一个abstract抽象类,用于读取、写入、映射和操作文件的通道
image.png
除了方框内的方法外,其余方法结尾abstract方法,构造方法protect无法调用
本身包含一个可读写、长度可变的字节序列,并且可以查询该文件的大小,写入字节byte超出文件当前大小则增加,截取文件则减小文件的大小
image.png
image.png

  • 该类的操作都是synchronized,阻塞的,并发安全的
  • 暂未定义打开现有文件或创建新文件的方法。只能通过FileInputStream、FileOutputStream、RandomAccessFile获取,且必须指定其时只读r、只写w、还是读写rw
  • 只读rFileChannel,通过FileInputStream
        FileChannel channel = new FileInputStream(Paths.get("").toString()).getChannel();
        FileChannel channel1 = new RandomAccessFile(Paths.get("").toString(),"r").getChannel();
  • 只写rFileChannel,通过FileOutputStream
        FileChannel channel = new FileOutputStream(Paths.get("").toString()).getChannel();
  • 读写rFileChannel,通过RandomAccessFile
        FileChannel channel1 = new RandomAccessFile(Paths.get("").toString(),"rw").getChannel();
  • FileChannel的实现类
    一个FileChannelImpl和一个匿名内部类
    image.png

设置位置position和大小size

  • size 当前ChannelFile的大小
  • position可以>FielChannel的size
    此时如果继续读取,则返回-1,到大文件末尾,如果继续写入,则在posiiton位置继续写入,扩容FileChannel容纳新的Byte字节
//position和size
class BH {
    public static void main(String[] args) {
        try (FileChannel fileChannel = new FileOutputStream(Paths.get("./BH.txt").toString()).getChannel()) {
            System.out.println(fileChannel.size());
            fileChannel.write(ByteBuffer.wrap("123".getBytes(StandardCharsets.UTF_8)));
            System.out.println(fileChannel.size());
            fileChannel.position(5);
            System.out.println(fileChannel.size());
            fileChannel.write(ByteBuffer.wrap("456".getBytes(StandardCharsets.UTF_8)));
            System.out.println(fileChannel.size());
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("===========================");
        try (FileChannel fileChannel = new FileInputStream(Paths.get("./BH.txt").toString()).getChannel()) {
            System.out.println(fileChannel.size());
            System.out.println(fileChannel.position());
            fileChannel.position(10);
            System.out.println(fileChannel.size());
            System.out.println(fileChannel.read(ByteBuffer.allocate(2)));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

结果如下,读返回-1,写则扩容
image.png
最终BH.txt内容为
image.png

写操作int write(ByteBuffer src)

是继承了WritableByteChannel方法

    public abstract int write(ByteBuffer src) throws IOException;
  • 加了synchronized锁,线程安全阻塞

  • 是从FileChannel的position位置开始写入的

class BA{
    public static void main(String[] args)  {
       try( FileChannel fileChannel = new FileOutputStream(Paths.get("./a.txt").toString()).getChannel()){
           ByteBuffer byteBuffer = ByteBuffer.wrap("abcde".getBytes());
           System.out.println(fileChannel.position());
           //写入的文件为abcde
           System.out.println(fileChannel.write(byteBuffer));
           System.out.println(fileChannel.position());
           //position设置为3,因为d的下标为3
           fileChannel.position(3);
           System.out.println(fileChannel.position());
           //初始化Buffer属性,从0开始
           byteBuffer.rewind();
           //abcde从c之后,d开始又写abcde,最后为abcabcde
           System.out.println(fileChannel.write(byteBuffer));
           System.out.println(fileChannel.position());
       } catch (IOException e) {
           e.printStackTrace();
       }
    }
}

image.png
最终a.txt文件的内容为abcabcde
image.png

  • 将缓冲区的remaining字节序列写入通道的当前位置
class BB{
    public static void main(String[] args) {
        try (FileChannel fileChannel = new FileOutputStream(Paths.get("./b.txt").toString()).getChannel()) {
            ByteBuffer byteBuffer1 = ByteBuffer.wrap("abcde".getBytes(StandardCharsets.UTF_8));
            ByteBuffer byteBuffer2 = ByteBuffer.wrap("12345".getBytes(StandardCharsets.UTF_8));
            fileChannel.write(byteBuffer1);
            byteBuffer2.position(2);
            byteBuffer2.limit(3);
            fileChannel.position(3).write(byteBuffer2);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

最终结果为下图所示
先写入abcde,后FileChannel的postion跳至3下标即d,最后覆盖写入byteBuffer2的下标2至3之前即为3,最后为abc3e
image.png

读操作int read(ByteBuffer dst)

是继承了ReadableByteChannel方法

    public int read(ByteBuffer dst) throws IOException;
  • 加了synchronized锁,线程安全阻塞

  • 从FileChannel的当前位置position开始读取

  • 将字节Byte放入ByteBuffer当前位置position

class BC{
    public static void main(String[] args) {
        //将abc3e读入
        try (FileChannel fileChannel = new FileInputStream(Paths.get("./b.txt").toString()).getChannel()) {

            fileChannel.position(2);
            ByteBuffer byteBuffer = ByteBuffer.allocate(5);
            byteBuffer.position(1);
            fileChannel.read(byteBuffer);
            byteBuffer.clear();
            for (int i = 0; i < byteBuffer.limit(); i++) {
                System.out.print(byteBuffer.get()+" ");
            }
            byteBuffer.clear();
            System.out.println();
            for (int i = 0; i < byteBuffer.limit(); i++) {
                System.out.print((char) byteBuffer.get()+" ");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

1.从abc3e取出位置为2即c3e
2.从Buffer的下标为1的地方存,中间缓冲Buffer的下标为数组,此时用直接缓冲Buffer会报错
3.存完之后因为容量为5,只有3,故Buffer的底层数组int类型初始化为0
image.png

  • 当Buffer的renaming容量<读取的FileChannel容量,则只读取renaming的容量大小

批量写write(ByteBuffer[] srcs)

实现了GatheringByteChannel接口的功能

public final long write(ByteBuffer[] srcs) throws IOException {
        return write(srcs, 0, srcs.length);
    }
  • 可以将ByteBuffer数组剩余的remaining的字节写入通道的当前问题
  • 默认追加写入
    image.png
  • 是加了锁同步的,线程安全阻塞
//批量写position
class BD {
    public static void main(String[] args) {
        try (FileChannel fileChannel = new FileOutputStream(Paths.get("./BD.txt").toString()).getChannel()) {
            fileChannel.write(ByteBuffer.wrap("12345".getBytes(StandardCharsets.UTF_8)));
            fileChannel.position(2);
            ByteBuffer byteBuffer1 = ByteBuffer.wrap("ABCDE".getBytes(StandardCharsets.UTF_8));
            ByteBuffer byteBuffer2 = ByteBuffer.wrap("QWERDF".getBytes(StandardCharsets.UTF_8));
            byteBuffer1.position(1).limit(3);
            byteBuffer2.position(2).limit(4);
            ByteBuffer[] byteBuffers = {byteBuffer2,byteBuffer1};
            fileChannel.write(byteBuffers);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

结果如下,从12345的2之后开始写ER即12ER5,之后又继续写BC,最终为12ERBC
image.png

批量读read(ByteBuffer[] dsts)

实现了ScatteringByteChannel接口的功能

    public final long read(ByteBuffer[] dsts) throws IOException {
        return read(dsts, 0, dsts.length);
    }
  • 将一个通道Channel读取到多个Buffer中
  • 返回读取得的字节数目,如果为-1则代表到末尾
  • 从通道当前位置position读取,放入Buffer当前position位置
  • Buffer[]缓冲区的总共remaining剩余多少就读取多少,不会多读字节
//批量读position
class BE {
    public static void main(String[] args) {
        //内容为12ERBC
        try (FileChannel fileChannel = new FileInputStream(Paths.get("./BD.txt").toString()).getChannel()) {
            ByteBuffer byteBuffer1 = ByteBuffer.allocate(1);
            ByteBuffer byteBuffer2 = ByteBuffer.allocate(2);
            byteBuffer2.limit(1);
            ByteBuffer byteBuffer3 = ByteBuffer.allocate(3);
            byteBuffer3.position(2);
            ByteBuffer[] byteBuffers = {byteBuffer1, byteBuffer2, byteBuffer3};
            fileChannel.position(1);
            System.out.println(fileChannel.position());
            System.out.println(fileChannel.read(byteBuffers));
            System.out.println(fileChannel.position());
            byteBuffer1.clear();
            byteBuffer2.clear();
            byteBuffer3.clear();
            for (int i = 0; i < byteBuffer1.limit(); i++) {
                System.out.print((char) byteBuffer1.get() + " ");
            }
            System.out.println();
            for (int i = 0; i < byteBuffer2.limit(); i++) {
                System.out.print((char) byteBuffer2.get() + " ");
            }
            System.out.println();
            for (int i = 0; i < byteBuffer3.limit(); i++) {
                System.out.print((char) byteBuffer3.get() + " ");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

初始12ERBC为FileChannel位置position为1则从2ERBC开始,因为总共Buffer的remaining的容量为3,所以FileChannel不会走完,只走到position为4的位置。返回的结果为走过的字节数目为3
image.png

部分批量写long write(ByteBuffer[] srcs, int offset, int length)

也是实现了GatheringByteChannel接口的功能,说明有一部分属性相同
image.png
先从offset到lengthBuffer数组下标写,写不完则写剩余的Buffer的remaining容量

    public abstract long write(ByteBuffer[] srcs, int offset, int length)
        throws IOException;
  • offset 第一个写的缓冲区所在的Buffer数组下标
  • length 要访问的最大缓冲区,必须为在offset和Buffer[]长度之间

部分批量读long read(ByteBuffer[] dsts, int offset, int length)

也是实现了ScatteringByteChannel接口的功能,一部分属性相同
从该通道的当前文件位置开始读取字节,然后使用实际读取的字节数更新文件位置。 否则,此方法的行为与ScatteringByteChannel接口中指定的完全相同

public abstract long read(ByteBuffer[] dsts, int offset, int length)
        throws IOException;
  • 和批量写的属性差不多
  • 当读的FileChannel容量>所有的Buffer的remaining时则只读取Buffer[]数组的总remaining值

指定位置position写

新增的抽象方法,写之后并不改变原来的Channel位置position

    public abstract int write(ByteBuffer src, long position) throws IOException;
  • 如果写入的Buffer大于Channel则Channel会扩容
  • 从给定的Buffer的position开始,也从给定的Channelposition开始,但是Channel的position并不会改变
//指定position写
class BG {
    public static void main(String[] args) {
        try (FileChannel fileChannel = new FileOutputStream(Paths.get("./BG.txt").toString()).getChannel()) {
            fileChannel.position(1);
            System.out.println(fileChannel.position());
            ByteBuffer byteBuffer = ByteBuffer.wrap("ABCDE".getBytes(StandardCharsets.UTF_8));
            byteBuffer.position(2);
            fileChannel.write(byteBuffer,3);
            System.out.println(fileChannel.position());
            System.out.println(byteBuffer.position());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注意此时Channel的position并没有改变,Buffer的position还是会改变
image.png
生成的文件前3个字母也为NULL
image.png

指定位置position读

新增的抽象方法,读之后并不改变原来的Channel位置position

    public abstract int read(ByteBuffer dst, long position) throws IOException;
  • 和指定位置position写差不多
  • 读取之后不改变Channel的位置position

截断FileChannel

由SeekableByteChannel接口而来

    public abstract FileChannel truncate(long size) throws IOException;
  • 如果size>FileChannel的size,则不做任何改变
  • 如果size<FileChannel的size,则从0开始截取到size长度
  • 如果size<position,则positon改变为size大小
class BI {
    public static void main(String[] args) {
        try (FileChannel fileChannel = new FileOutputStream(Paths.get("./BI.txt").toString()).getChannel()) {
            fileChannel.write(ByteBuffer.wrap("12345".getBytes(StandardCharsets.UTF_8)));
            fileChannel.position(4);
            System.out.println(fileChannel.size() + " " + fileChannel.position());
            fileChannel.truncate(3);
            System.out.println(fileChannel.size() + " " + fileChannel.position());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

长度为5,position为4截取3个长度变为长度为3,positon为3
image.png
BI.txt也变为123
image.png

ReadableByteChannel数据读取后写入其他WritableByteChannel字节Byte通道

调用者为ReadableByteChannel。从此Channel传输到可写的Channel
和write类似,只是是从Channel转移而不是从Buffer中转移
此方法较高效,很多系统可以从文件系统缓存传输到目标通道,无需操作复制字节byte

    public abstract long transferTo(long position, long count,
                                    WritableByteChannel target)
        throws IOException;

image.png
吐槽一下,方法签名入参格式为什么不统一呢

  • postion 方法调用的Channel的位置即需要转移的Channel位置
  • count 需要传输的字节大小
  • target 目的Channel

从调用的Channel的postion位置开始传输count个字节到target的Channel中

  • position>调用的Channel的size则不传输
  • 此方法不会更改调用的Channel实际的position位置
  • 方法返回实际传输的字节数,可能为零
//从此Channel传输到可写的Channel
class BJ {
    public static void main(String[] args) {
                    //12345
        try (FileChannel fileChannel1 = new RandomAccessFile("./BJ1.txt", "rw").getChannel();
                    //ABCDE
             FileChannel fileChannel2 = new RandomAccessFile("./BJ2.txt", "rw").getChannel()) {
            fileChannel1.position(2);
            fileChannel2.position(3);
            System.out.println(fileChannel1.position()+" "+fileChannel2.position());
            //从1中的postion为2的位置开始取5个字节
            //2变为ABC345
            System.out.println(fileChannel1.transferTo(2, 5, fileChannel2));
            System.out.println(fileChannel1.position()+" "+fileChannel2.position());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

调用后fileChannel1的position位置没有改变
image.png

将WritableByteChannel写入从其他ReadableByteChannel读取的数据

调用者为WritableByteChannel。从可读的Channel传输到此Channel
注意入参Channel的位置和上面不同,在第一个

    public abstract long transferFrom(ReadableByteChannel src,
                                      long position, long count)
        throws IOException;
  • src 源通道

  • position 文件中开始传输的位置; 必须是非负数,即调用方开始写入的位置

  • count 要传输的最大字节数; 必须是非负数

  • 调用的channel即被写入的channel是从postion位置开始写入的,写入之后该Channel本身的postion位置不会会改变

  • 读取Channel之后,被读取的Channel本身的position位置会改变

  • 返回实际读取的字节数

//从可读的Channel传输到此Channel
class BK {
    public static void main(String[] args) {
            //12345
        try (FileChannel fileChannel1 = new RandomAccessFile("./BK1.txt", "rw").getChannel();
             //ABCDE
             FileChannel fileChannel2 = new RandomAccessFile("./BK2.txt", "rw").getChannel()) {
            fileChannel1.position(2);
            //从2中的postion为2的位置开始取5个字节,字节不够只能取到DE
            fileChannel2.position(3);
            System.out.println(fileChannel1.position() + " " + fileChannel2.position());
            //从1中的当前position即从4和之后的字节,最终为1234DE
            System.out.println(fileChannel1.transferFrom(fileChannel2,4, 5));
            System.out.println(fileChannel1.position() + " " + fileChannel2.position());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

image.png

force(boolean metaData)减少数据丢失,强刷

    public abstract void force(boolean metaData) throws IOException;

在调用write时,是将Buffer或者Channel的数据保存在内核缓存中,等待CPU在特定的时间片执行(可以提升运行效率),有可能会造成断电时有一部分数据未保存在硬盘上。
该方法强制同步刷新到硬盘上,减少数据丢失,性能会下降

  • metaData – 如果为true,则需要此方法来强制更改要写入存储的文件内容和元数据; 否则,它只需要强制写入内容更改。依赖于底层实现
//验证force
class CF {
    public static void main(String[] args) {
        try (FileChannel fileChannel = new FileOutputStream(Paths.get("./CF1.txt").toString()).getChannel()) {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 2000; i++) {
                fileChannel.write(ByteBuffer.wrap("ABCDE".getBytes(StandardCharsets.UTF_8)));
            }
            System.out.println(System.currentTimeMillis() - start);
        } catch (IOException e) {
            e.printStackTrace();
        }
        try (FileChannel fileChannel = new FileOutputStream(Paths.get("./CF2.txt").toString()).getChannel()) {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 2000; i++) {
                fileChannel.write(ByteBuffer.wrap("ABCDE".getBytes(StandardCharsets.UTF_8)));
                fileChannel.force(false);
            }
            System.out.println(System.currentTimeMillis() - start);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

同样的结果时间差距有5倍,如果数据大,差距会更大
image.png

将Channel的一个区域直接映射到内存map

MappedByteBuffer map(MapMode mode,
                                         long position, long size)
        throws IOException;

直接存在于内存映射中
image.png

打开文件open

	public static FileChannel open(Path path,
                                   Set<? extends OpenOption> options,
                                   FileAttribute<?>... attrs)
	public static FileChannel open(Path path, OpenOption... options)
        throws IOException

OpenOption仅仅为定义的标志接口,无任何方法
image.png
其下有3个实现类分别为拓展、标准、符号链接类Option,其实现enum内定义了固定的名称
image.png
标准enum类定义了一什么样子的方式打开文件,或以只读/写/稀疏文件方式等,或者以不存在创建等方式。
拓展类enum根据不同JDK平台有不同的拓展

isOpen判断Channel是否打开

生成新的Channel后,调用close方法前是open状态的

FileChannel锁

获取此FileChannel给定区域上的锁定
分为独占锁和共享锁,文件锁要么是独占要么是共享。独占锁具有排他性,共享锁只能继续施加共享锁
如果操作系统不支持共享锁则会吧共享锁升级为独占锁。
image.png
文件锁是以JVM来保持的,不适用于同一个JVM的多线程文件访问,如果继续获得锁会抛出OverlappingFileLockException

public abstract FileLock lock(long position, long size, boolean shared)
        throws IOException;
  • position 锁定区域开始的位置; 必须是非负数
  • size 锁定区域的大小; 必须为非负数,并且总和位置+大小必须为非负数
  • shared true请求共享锁,在这种情况下,此通道必须打开以进行读取(并且可能写入); false请求排他锁,在这种情况下,此通道必须打开以进行写入(可能还有读取)
//返回此锁是否是共享锁
  public final boolean isShared() {
        return shared;
    }

接下来验证FileLock锁的特性

FileLock锁的特性

  • 同一JVM不适用FIleLock
//同一个JVM多线程不适用于锁
class CA{
    public void testLock(){
        try (FileChannel fileChannel = new RandomAccessFile("./CA.txt","rw").getChannel()) {
            fileChannel.lock(0,1,false);
            try {
                System.out.println(Thread.currentThread().getName()+"获得锁");
                Thread.sleep(Integer.MAX_VALUE);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        CA ca = new CA();
        new Thread(()->ca.testLock()).start();
        new Thread(()->ca.testLock()).start();
        System.out.println("main线程结束");
    }
}

同一JVM启用多个线程后,如果尝试锁上一个对象会跑RuntimeException的子类OverlappingFileLockException
image.png
再次运行另一个main方法

//同一个JVM多线程不适用于锁
class CA{
    public void testLock(){
        try (FileChannel fileChannel = new RandomAccessFile("./CA.txt","rw").getChannel()) {
            fileChannel.lock(0,1,false);
            try {
                System.out.println(Thread.currentThread().getName()+"获得锁");
                Thread.sleep(Integer.MAX_VALUE);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        CA ca = new CA();
        new Thread(()->ca.testLock()).start();
//        new Thread(()->ca.testLock()).start();
        System.out.println("main线程结束");
    }
}
class CB{
    public static void main(String[] args) {
        CA ca = new CA();
        new Thread(()->ca.testLock()).start();
//        new Thread(()->ca.testLock()).start();
        System.out.println("main线程结束");
    }
}

此时CB类的main因为是独享锁所以无法获取
image.png

  • 如果lock被调用期间,另一个线程关闭通道则会抛出异常

  • 如果线程获得锁之后又被Interrupt,则会抛出FileLockInterruptionException

  • 共享锁自己不能写共享的范围

  • 共享锁其他线程不能写(不同JVM内)

  • 独占锁自己可以写读

  • 独占锁其他线程不能写读(不同JVM内)

  • 独占锁和其他锁都是互斥关系,不能重复添加锁

FileLock类

image.png
每次通过FIleChannel的lock或者tryLock方法会创建一个FileLock对象

  • 调用release方法可用于关闭锁定的通道
  • isVaild测试锁定的有效性
  • isShared测试是独占还是共享
  • overlaps测试锁定范围是否于现有锁定重叠
  • 多个并发线程可以安全使用该类
  • 依赖于JVM维护,同一JVM内的多线程不适用
  • 具有平台依赖性,依赖于具体的平台实现

FileLock tryLock()非阻塞加锁

和Lock接口的trylock方法类似,立即返回结果不会阻塞

  • 如果另一个程序保持着一个重叠锁,无法获得则返回null
  • 其他任何原因无法获得锁则抛出对应的异常
  • 和FIleLock的lock方法一样,由JVM维护,不适用于同一JVM体系内的多线程

这个家伙很懒,啥也没有留下😋