NIO源码分析:SelectionKey(1)

}

AbstractSelectionKey

=====================

AbstractSelectionKey继承了SelectionKey类,它也是一个抽象类,相比其他俩个类,它的代码就简洁多了,它只有一个属性:

//用于判断该SelectionKey是否有效,true为有效,false为无效,默认有效

private volatile boolean valid = true;

AbstractSelectionKey除开构造器方法,只要三个实现方法:

//判断该SelectionKey是否有效

public final boolean isValid() {

return valid;

}

//将该SelectionKey设为无效

void invalidate() {

valid = false;

}

//将该SelectionKey从选择器中删除

//注意,删除的SelectionKey并不会马上从选择器上删除,而是会加入一个需删除键的集合中,等到下一次调用选择方法才会将它从选择器中删除,至于具体实现会在选择器源码分析章节中讲

public final void cancel() {

synchronized (this) {

if (valid) {

valid = false;

((AbstractSelector)selector()).cancel(this);

}

}

}

SelectionKeyImpl

====================

SelectionKeyImpl是SelectionKey的最终实现类,它继承了AbstractSelectionKey类,在该类中不仅实现了SelectionKey和抽象类的方法,而且扩展了其他方法。

SelectionKeyImpl的属性

=======================

SelectionKeyImpl中有5个新的属性:

//该SelectionKey对应的通道

final SelChImpl channel;

//该SelectionKey注册的选择器

public final SelectorImpl selector;

//该SelectionKey在注册选择器中储存SelectionKey集合中的下标索引,当该SelectionKey被撤销时,index为-1

private int index;

//SelectionKey的关注操作符

private volatile int interestOps;

//SelectionKey的预备操作符

private int readyOps;

从上面属性中可以看到,SelectionKeyImpl有俩个操作符属性:关注操作符interestOps预备操作符readyOps

interestOps是储存通道的注册方法register(Selector sel, int ops)输入的ops参数,可以在register方法的最终实现中看出,代表程序需选择器对通道关注的操作事件。

public final SelectionKey register(Selector sel, int ops, Object att) throws ClosedChannelException

{

synchronized (regLock) {

if (!isOpen())

throw new ClosedChannelException();

if ((ops & ~validOps()) != 0)

throw new IllegalArgumentException();

if (blocking)

throw new IllegalBlockingModeException();

SelectionKey k = findKey(sel);

if (k != null) {

//将输入的参数ops储存在SelectionKey的interestOps属性中

k.interestOps(ops);

k.attach(att);

}

if (k == null) {

synchronized (keyLock) {

if (!isOpen())

throw new ClosedChannelException();

k = ((AbstractSelector)sel).register(this, ops, att);

addKey(k);

}

}

return k;

}

}

readyOps是通道实际发生的操作事件,当我们对选择器Selector.select()方法层层追溯,到达该方法的最终实现doSelect(long var1)方法,会发现doSelect方法调用了一个updateSelectedKeys()方法来更新选择器的SelectionKey集合,而updateSelectedKeys方法又调用了updateSelectedKeys(this.updateCount)方法来进行实际的更新操作,而updateSelectedKeys方法最终通过processFDSet方法来实现更新。在processFDSet方法里有两个方法调用实现了SelectionKey里readyOps属性的更新:translateAndSetReadyOps(用于设置readyOps)translateAndUpdateReadyOps(用于更新readyOps)

public boolean translateAndUpdateReadyOps(int var1, SelectionKeyImpl var2) {

return this.translateReadyOps(var1, var2.nioReadyOps(), var2);

}

public boolean translateAndSetReadyOps(int var1, SelectionKeyImpl var2) {

return this.translateReadyOps(var1, 0, var2);

}

通过观察两个方法,可以发现它们都调用了translateReadyOps方法:

/**

  • @param var1 该通道发生的事件类型,为Net中的POLL类型属性,这里需注意的有3个POLL类型属性:POLLIN、

  • POLLOUT、POLLCONN,分别对应SelectionKey的OP_READ、OP_WRITE、OP_CONNECT

  • @param var2 translateAndUpdateReadyOps中该参数为var3的readyOps,在translateAndSetReadyOps * 中该参数为0

  • @param var3 需修改的SelectionKey

*/

public boolean translateReadyOps(int var1, int var2, SelectionKeyImpl var3) {

int var4 = var3.nioInterestOps();

int var5 = var3.nioReadyOps();

int var6 = var2;

if ((var1 & Net.POLLNVAL) != 0) {

return false;

} else if ((var1 & (Net.POLLERR | Net.POLLHUP)) != 0) {

var3.nioReadyOps(var4);

this.readyToConnect = true;

return (var4 & ~var5) != 0;

} else {

//var1 & var2 !=0 类似于 var1 == var2,或var1=0或var2=0

//判断发生的事件是否是Net.POLLIN,如果是则判断该通道对应的SelectionKey的readyOps是否等于1(OP_READ的值)

if ((var1 & Net.POLLIN) != 0 && (var4 & 1) != 0 && this.state == 2) {

var6 = var2 | 1;//等同于:var2 | OP_READ

}

//判断发生的事件是否是Net.POLLCONN,如果是则判断该通道对应的SelectionKey的readyOps是否等于8(OP_CONNECT的值)

if ((var1 & Net.POLLCONN) != 0 && (var4 & 8) != 0 && (this.state == 0 || this.state == 1)) {

var6 |= 8;//等同于:var6 | OP_CONNECT

this.readyToConnect = true;

}

//判断发生的事件是否是Net.POLLOUT,如果是则这判断该通道对应的SelectionKey的readyOps是否等于4(OP_OP_WRITE的值)

if ((var1 & Net.POLLOUT) != 0 && (var4 & 4) != 0 && this.state == 2) {

var6 |= 4;//等同于:var6 | OP_READ

}

var3.nioReadyOps(var6);

return (var6 & ~var5) != 0;

}

}

看完上面源码可以发现,在判断通道实际发生事件的POLL类型时,还需判断该POLL类型对应的SelectionKey的ops类型是否与该SelectionKey的关注键interestOps相同,当所有条件满足时再将var6与其ops类型按位或,最后将该SelectionKey即var3的预备键readyOps设为var6。总结起来就是:

当通道实际发生的操作事件类型不等于该通道对应的SelectionKey的关注键interestOps时, 预备键readyOps不会被修改,且该发生事件的通道对应的SelectionKey也不会被加入Selector的selectedKeys集合中(从下面代码中可看出),但readyOps不一定就等于interestOps,因为调用interestOps的修改或设置方法时并不会同时修改readyOps。为了防止在进行select操作时,有另一个线程修改了某一SelectionKey的interestOps属性,在interestOps前添加了volatile修饰符,保证其可见性。

//这里是processFDSet方法中的一段代码,var10为SelectionKey

/*可以看出在进行了translateAndSetReadyOps或translateAndUpdateReadyOps方法设置readyOps属性后,

  • 为了防止这时有另一线程修改了interestOps或readyOps为0,还判断了SelectionKey的readyOps和interestOps是否相同,

*/相同才把该SelectionKey加入到选择器的selectedKeys属性中

if (var9.clearedCount != var1) {

var10.channel.translateAndSetReadyOps(var4, var10);

if ((var10.nioReadyOps() & var10.nioInterestOps()) != 0) {

WindowsSelectorImpl.this.selectedKeys.add(var10);

var9.updateCount = var1;

++var6;

}

} else {

var10.channel.translateAndUpdateReadyOps(var4, var10);

if ((var10.nioReadyOps() & var10.nioInterestOps()) != 0) {

WindowsSelectorImpl.this.selectedKeys.add(var10);

var9.updateCount = var1;

++var6;

}

}

SelectionKeyImpl的方法

===================

在SelectionKeyImpl中除开实现了SelectionKey抽象方法的简单方法外,需要关注几个较为重要的方法:

//确保该SelectionKey是有效的,如无效则会之间抛出异常

//在几个关于SelectionKey的属性方法中都有调用,如interestOps() 、interestOps(int var1)、readyOps()

private void ensureValid() {

if (!this.isValid()) {

throw new CancelledKeyException();

}

}

//设置readyOps的方法,不会确保该SelectionKey的有效性

public void nioReadyOps(int var1) {

this.readyOps = var1;

}

//该方法是SelectionKeyImpl自定义的获取readyOps方法,不同于readyOps(),它不会确保SelectionKey的有效性

public int nioReadyOps() {

return this.readyOps;

}

//获取interestOps的方法,interestOps()会通过调用该方法进行获取,是最终实现获取nterestOps的方法

public int nioInterestOps() {

return this.interestOps;

}

在SelectionKeyImpl类里,设置interestOps相比设置readyOps较为复杂,因为他调用了两个通道的方法validOps()和translateAndSetInterestOps(int var1, SelectionKeyImpl var2):

//interestOps(int var1)中调用了该方法来设置interestOps,是最终实现设置interestOps的方法

public SelectionKey nioInterestOps(int var1) {

if ((var1 & ~this.channel().validOps()) != 0) {

throw new IllegalArgumentException();

} else {

this.channel.translateAndSetInterestOps(var1, this);

this.interestOps = var1;

return this;

}

}

首先先来看看nioInterestOps方法中判断语句内的代码块:

(var1 & ~this.channel().validOps()) != 0

这里的this.channel有5个可能的实现类:SeverSocketChannel、SocketChannel、SinkChannel、SourceChannel、DatagramChannel,因为不同的通道可能只会发生对应的操作事件,如SeverSocketChannel只会发生OP_ACCEPT操作,SocketChannel会发生OP_READ、OP_WRITE和OP_CONNECT,而validOps()就是返回通道对应可能发生的操作事件(有效操作事件):

//SeverSocketChannel

public final int validOps() {

return SelectionKey.OP_ACCEPT;

}

//SocketChannel

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

[外链图片转存中…(img-dj3zuAwf-1715857009107)]

[外链图片转存中…(img-IMUOUl0L-1715857009108)]

[外链图片转存中…(img-uNKCnn5l-1715857009108)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值