缓冲区buffer
缓冲区基础
属性
0 <= mark <= position <= limit <= capacity
缓冲区api
- 级联类设计:返回对象的引用
存取
- get()和put()方法,并没有在buffer中声明(参数类型和返回类型对于每个子类都是唯一的)。
填充
- flip:“对当前位置设置限制,然后将该位置设置为零。” 将一个能够继续添加数据元素的填充状态的缓冲区翻转成一个准备读出元素的释放状态。
- rewind:将当前位置设置为0,后退重读被翻转的缓冲区数据。
释放
- remaining():将告知您从当前位置到上界还剩余的元素数目
- 缓冲区不是线程安全的;如果多线程进行存取,需要进行同步处理。
- clear()将上界设置为容量的值,并把位置设置为0,并不改变缓冲区的任何元素。
压缩
- compact():将 position 与 limit之间的数据复制到buffer的开始位置,复制后 position = limit -position,limit = capacity.
调用compact()的作用是丢弃已经释放的数据,保留未释放的数据,并使缓冲区对重新填充容量准备就绪
标记
标记,使缓冲区能够记住一个位置并在之后将其返回。
比较
- equals():如果每个缓冲区中剩余的内容相同,那么函数将返回true,否则返回false。
- 两个缓冲区相等,充要条件
- 存储的对象类型相同
- 剩余同样数量的元素,buffer容量不需要相同,剩余数据的索引也不必相同,但每个缓冲区中剩余的元素的数目必须相同。
- 每个缓冲区中应被get函数返回的属于数据元素序列必须一致。
- compareTo():不允许不同对象进行比较;比较针对每个缓冲区内剩余元素进行的。
批量移动
- get移动元素
- 在调用get之前必须先查询缓冲区中的元素数量。
- put:向缓冲区填充元素
创建缓冲区
- MappedByteBuffer是ByteBuffer专门用于内存映射的一种特例。
- wrap()
复制缓冲区
字节缓冲区
通道Channel
Channel用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效地传输数据。
通道基础
使用通道
- 通道可以以阻塞(blocking)或非阻塞(nonblocking)模式运行。非阻塞模式的通道永远不会让调用的线程休眠。请求的操作要么立即完成,要么返回一个结果表明未进行任何操作。只有面向流的(stream-oriented)的通道,如sockets和pipes才能使用非阻塞模式。
关闭通道
- 调用通道的close()方法时,可能会导致在通道关闭底层I/O服务的过程中线程暂时阻塞
- isOpen()方法来测试通道的开放状态。
- 当前线程的interrupt status可以通过调用静态的Thread.interrupted( )方法清除。
Scatter/Gather
文件通道
- 文件通道总是阻塞式的,因此不能被置于非阻塞模式
- FileChannel对象是线程安全(thread-safe)的。
访问文件
文件锁
- 不带参数的简单形式的lock()方法是一种在整个文件上请求独占锁的便捷方法,锁定区域等于它能达到的最大范围。
- 调用带参数的Lock()方法会指定文件内部锁定区域的开始position以及锁定区域的size。
- tryLock是Lock的非阻塞变体
- 尽管一个FileLock对象是与某个特定的FileChannel实例关联的,它所代表的锁却是与一个底层文件关联的,而不是与通道关联。
内存映射文件
- 通过内存映射机制来访问一个文件会比使用常规方法读写高效得多,甚至比使用通道的效率都
84
高。因为不需要做明确的系统调用,那会很消耗时间。更重要的是,操作系统的虚拟内存可以自动缓存内存页(memory page)。这些页是用系统内存来缓存的,所以不会消耗Java虚拟机内存堆(memory heap)。 - 结合文件锁定来保护关键区域和控制事务原子性,那您将能了解到内存映射缓冲区如何可以被很好地利用。
channel-to-channel传输
- FileChannel类有这两个方法。transferTo()和transferFrom()
socket通道
- 并非所有的socket都有一个关联的通道。
非阻塞模式
- Socket通道是线程安全的;任何时候都只有一个读操作和一个写操作在进行中
DatagramChannel
- 与面向流的的socket不同,DatagramChannel可以发送单独的数据报给不同的目的地址
- 如果已经安装了一个安全管理器,那么它会进行权限检查。之后,每次send/receive时就不会再有安全检查了,因为来自或去到任何其他地址的包都是不允许的.
- 下面列出了一些选择数据报socket而非流socket的理由:
- 您的程序可以承受数据丢失或无序的数据。
- 您希望“发射后不管”(fire and forget)而不需要知道您发送的包是否已接收。
- 数据吞吐量比可靠性更重要。
- 您需要同时发送数据给多个接受者(多播或者广播)。
- 包隐喻比流隐喻更适合手边的任务。
管道
- Pipe类创建一对提供环回机制的Channel对象
- 管道可以被用来仅在同一个Java虚拟机内部传输数据
- Pipes的另一个有用之处是可以用来辅助测试
通道工具类
channels的几种静态的工厂方法以使通道可以更加容易地同流和读写器互联
选择器
选择器提供选择执行已经就绪的任务的能力,这使得多元 I/O 成为可能。
选择器基础
选择器
selector
选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。当这么做的时候,可以选择将被激发的线程挂起,直到有就绪的的通道。
选择器维护了一个需要监控的通道的集合.
可选择通道
一个通道可以被注册到多个选择器上,但对每个选择器而言只能被注册一次
选择键
选择键封装了特定的通道与特定的选择器的注册关系。
通道在被注册到一个选择器上之前,必须先设置为非阻塞模式(通过调用configureBlocking(false))
选择器才是提供管理功能的对象,而不是可选择通道对象。选择器对象对注册到它之上的通道执行就绪选择,并管理选择键。
建立选择器
- 您可以通过调用validOps( )方法来获取特定的通道所支持的操作集合
- 通道不会在键被取消的时候立即注销;必在键可能被取消的情况下检查SelectionKey对象的状态。
- 调用isRegistered( )方法来检查一个通道是否被注册到任何一个选择器上
- 当通道关闭时,所有相关的键会自动取消
- 当选择器关闭时,所有被注册到该选择器的通道都将被注销,并且相关的键将立即被无效化
- interset集合永远不会被选择器改变,但您可以通过调用interestOps()方法并传入一个新的比特掩码参数来改变它。
- 不能直接改变键的ready集合
如果选择键的存续时间很长,但您附加的对象不应该存在那么长时间,请记得在完成后清理附件。否则,您附加的对象将不能被垃圾回收,您将会面临内存泄漏问题。
- SelectionKey对象是线程安全的,但知道修改interest集合的操作是通过Selector对象进行同步的是很重要的
选择过程
- 每一个Selector对象维护三个键的集
- 在一个刚初始化的Selector对象中,这三个集合都是空的
已注册key集合
- 并不是所有已注册key都有效;不能直接修改。
已选择key集合
- 已注册key的子集.
- 每个键至少已经关联一个准备操作的通道.
- 键可以直接从这个集合中移除,但不能添加。
已取消key集合
- 已注册的键的集合的子集
- 这个集合是选择器对象的私有成员,因而无法直接访问
选择操作是当三种形式的select()中的任意一种被调用时,由选择器执行的。不管是哪一种形式的调用,下面步骤将被执行:
- 已取消的键的集合将会被检查。如果它是非空的,每个已取消的键的集合中的键将从另外两个集合中移除,并且相关的通道将被注销。这个步骤结束后,已取消的键的集合将是空的。
- 已注册的键的集合中的键的interest集合将被检查。在这个步骤中的检查执行过后,对interest集合的改动不会影响剩余的检查过程。
停止选择过程
wakeup
调用Selector对象的wakeup()方法将使得选择器上的第一个还没有返回的选择操作立即返回.
调用close
如果选择器的close( )方法被调用,那么任何一个在选择操作中阻塞的线程都将被唤醒,就像wakeup( )方法被调用了一样。与选择器相关的通道将被注销,而键将被取消。
调用interrupt
中断一个选择器与中断一个通道是不一样的;选择器不会改变任意一个相关的通道,它只会检查它们的状态
管理选择键
并发性
- 选择器对象是线程安全的,但它们包含的键集合不是。通过keys()和selectKeys()返回的键的集合是Selector对象内部的私有的Set对象集合的直接引用
- 在多线程的场景中,如果您需要对任何一个键的集合进行更改,不管是直接更改还是其他操作带来的副作用,您都需要首先以相同的顺序,在同一对象上进行同步
异步关闭能力
- 任何时候都有可能关闭一个通道或者取消一个选择键
- 关闭通道的过程不应该是一个耗时的操作
- 当一个通道关闭时,它相关的键也就都被取消了,并不会影响正在进行的select( )。
选择过程的可扩展性
场景1
想要将更多的线程来为通道提供服务
对所有的可选择通道使用一个选择器,并将对就绪通道的服务委托给其他线程。您只用一个线程监控通道的就绪状态并使用一个协调好的工作线程池来处理共接收到的数据。根据部署的条件,线程池的大小是可以调整的(或者它自己进行动态的调整)。对可选择通道的管理仍然是简单的,而简单的就是好的。
场景2
某些通道要求比其他通道更高的响应速度。
可以通过使用两个选择器来解决:一个为命令连接服务,另一个为普通连接服务。但这种场景也可以使用与第一个场景十分相似的办法来解决。与将所有准备好的通道放到同一个线程池的做法不同,通道可以根据功能由不同的工作线程来处理。它们可能可以是日志线程池,命令/控制线程池,状态请求线程池。