在使用Selector时首先需要通过静态方法open创建Selector对象

 public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}

可以看到首先是调用SelectorProvider的静态方法provider,得到一个Selector的提供者

 public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<SelectorProvider>() {
public SelectorProvider run() {
if (loadProviderFromProperty())
return provider;
if (loadProviderAsService())
return provider;
provider = sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});
}
}

这段代码的逻辑也比较简单,首先判断provider是否已经产生,若已经产生,则直接返回现有的;若没有,则需要调用AccessController的静态方法doPrivileged,该方法是一个native方法,就不说了;可以看到在实现的PrivilegedAction接口中的run方法,做了三次判断:

第一次是根据是系统属性,使用ClassLoader类加载:

 private static boolean loadProviderFromProperty() {
String cn = System.getProperty("java.nio.channels.spi.SelectorProvider");
if (cn == null)
return false;
try {
Class<?> c = Class.forName(cn, true,
ClassLoader.getSystemClassLoader());
provider = (SelectorProvider)c.newInstance();
return true;
} catch (ClassNotFoundException x) {
throw new ServiceConfigurationError(null, x);
} catch (IllegalAccessException x) {
throw new ServiceConfigurationError(null, x);
} catch (InstantiationException x) {
throw new ServiceConfigurationError(null, x);
} catch (SecurityException x) {
throw new ServiceConfigurationError(null, x);
}
}

先获取键值为"java.nio.channels.spi.SelectorProvider"的属性,若没有,则直接返回false;若设置了,则需要使用加载器直接加载系统属性设置的java.nio.channels.spi.SelectorProvider的实现类,再通过反射机制直接产生实例对象并赋值给静态成员provider,最后返回true。

第二次使用ServiceLoader加载:

 private static boolean loadProviderAsService() {
ServiceLoader<SelectorProvider> sl =
ServiceLoader.load(SelectorProvider.class,
ClassLoader.getSystemClassLoader());
Iterator<SelectorProvider> i = sl.iterator();
for (;;) {
try {
if (!i.hasNext())
return false;
provider = i.next();
return true;
} catch (ServiceConfigurationError sce) {
if (sce.getCause() instanceof SecurityException) {
// Ignore the security exception, try the next provider
continue;
}
throw sce;
}
}
}

有关ServiceLoader的加载过程可以看我的上一篇博客【Java】ServiceLoader源码分析,在这里我就不累赘了。
该方法调用ServiceLoader的load加载在"META-INF/services/"路径下指明的SelectorProvider.class的实现类(其实是懒加载,在迭代时才真正加载)得到ServiceLoader对象,通过该对象的带迭代器,遍历这个迭代器;可以看到若是迭代器不为空,则直接返回迭代器保存的第一个元素,即第一个被加载的类的对象,并赋值给provider,返回true;否则返回false;

第三次是使用的默认的SelectorProvider(windows环境为例):

 public class DefaultSelectorProvider {
private DefaultSelectorProvider() {
} public static SelectorProvider create() {
return new WindowsSelectorProvider();
}
}

可以看到直接返回了WindowsSelectorProvider赋值给provider ;

此时provider无论如何都已经有了,接下来就是调用provider的openSelector方法。

WindowsSelectorProvider的openSelector方法:

 public class WindowsSelectorProvider extends SelectorProviderImpl {
public WindowsSelectorProvider() {
} public AbstractSelector openSelector() throws IOException {
return new WindowsSelectorImpl(this);
}
}

可以看到仅仅是产生了WindowsSelectorImpl:

 WindowsSelectorImpl(SelectorProvider var1) throws IOException {
super(var1);
this.wakeupSourceFd = ((SelChImpl)this.wakeupPipe.source()).getFDVal();
SinkChannelImpl var2 = (SinkChannelImpl)this.wakeupPipe.sink();
var2.sc.socket().setTcpNoDelay(true);
this.wakeupSinkFd = var2.getFDVal();
this.pollWrapper.addWakeupSocket(this.wakeupSourceFd, 0);
}

WindowsSelectorImpl首先调用父类SelectorImpl的构造方法:

 protected Set<SelectionKey> selectedKeys = new HashSet();
protected HashSet<SelectionKey> keys = new HashSet();
private Set<SelectionKey> publicKeys;
private Set<SelectionKey> publicSelectedKeys; protected SelectorImpl(SelectorProvider var1) {
super(var1);
if (Util.atBugLevel("1.4")) {
this.publicKeys = this.keys;
this.publicSelectedKeys = this.selectedKeys;
} else {
this.publicKeys = Collections.unmodifiableSet(this.keys);
this.publicSelectedKeys = Util.ungrowableSet(this.selectedKeys);
} }

SelectorImpl同样调用父类AbstractSelector的构造:

 protected AbstractSelector(SelectorProvider provider) {
this.provider = provider;
}

此时的provider就是刚才产生的WindowsSelectorProvider对象;
在SelectorImpl中还会对其成员有一系列的赋值操作;
上述都完成后才继续完成WindowsSelectorImpl的构造。

WindowsSelectorImpl在进行this.wakeupSourceFd = ((SelChImpl)this.wakeupPipe.source()).getFDVal()之前,其wakeupPipe成员如下:

 private final Pipe wakeupPipe = Pipe.open();

wakeupPipe管道通过Pipe.open()赋值:

 public static Pipe open() throws IOException {
return SelectorProvider.provider().openPipe();
}

可以看到实际上 SelectorProvider.provider()的provider的openPipe方法,而这个provider就是WindowsSelectorProvider,而WindowsSelectorProvider继承自SelectorProviderImpl,openPipe方法是在SelectorProviderImpl里实现的:

 public Pipe openPipe() throws IOException {
return new PipeImpl(this);
}

该方法直接产生了PipeImpl对象,并将WindowsSelectorProvider对象传入进去:

 PipeImpl(SelectorProvider var1) throws IOException {
try {
AccessController.doPrivileged(new PipeImpl.Initializer(var1));
} catch (PrivilegedActionException var3) {
throw (IOException)var3.getCause();
}
}

可以看到这个构造方法实际上是以特权模式运行的PipeImpl的内部类Initializer的run方法(doPrivileged需要的参数是PrivilegedExceptionAction接口的实现类,该接口只有run方法):
Initializer 的初始化:

 private class Initializer implements PrivilegedExceptionAction<Void> {
private final SelectorProvider sp;
private IOException ioe; private Initializer(SelectorProvider var2) {
this.ioe = null;
this.sp = var2;
}
......
}

该构造方法给sp赋值为传入进来的WindowsSelectorProvider对象,令ioe=null;
其所实现的run方法如下:

 public Void run() throws IOException {
PipeImpl.Initializer.LoopbackConnector var1 = new PipeImpl.Initializer.LoopbackConnector();
var1.run();
if (this.ioe instanceof ClosedByInterruptException) {
this.ioe = null;
Thread var2 = new Thread(var1) {
public void interrupt() {
}
};
var2.start(); while(true) {
try {
var2.join();
break;
} catch (InterruptedException var4) {
;
}
} Thread.currentThread().interrupt();
} if (this.ioe != null) {
throw new IOException("Unable to establish loopback connection", this.ioe);
} else {
return null;
}
}

首先产生LoopbackConnector 对象,是Initializer的内部类,而且实现了Runnable接口:

 private class LoopbackConnector implements Runnable {
private LoopbackConnector() {
}
}

其实现的run方法如下:

 public void run() {
ServerSocketChannel var1 = null;
SocketChannel var2 = null;
SocketChannel var3 = null; try {
ByteBuffer var4 = ByteBuffer.allocate(16);
ByteBuffer var5 = ByteBuffer.allocate(16);
InetAddress var6 = InetAddress.getByName("127.0.0.1"); assert var6.isLoopbackAddress(); InetSocketAddress var7 = null; while(true) {
if (var1 == null || !var1.isOpen()) {
var1 = ServerSocketChannel.open();
var1.socket().bind(new InetSocketAddress(var6, 0));
var7 = new InetSocketAddress(var6, var1.socket().getLocalPort());
} var2 = SocketChannel.open(var7);
PipeImpl.RANDOM_NUMBER_GENERATOR.nextBytes(var4.array()); do {
var2.write(var4);
} while(var4.hasRemaining()); var4.rewind();
var3 = var1.accept(); do {
var3.read(var5);
} while(var5.hasRemaining()); var5.rewind();
if (var5.equals(var4)) {
PipeImpl.this.source = new SourceChannelImpl(Initializer.this.sp, var2);
PipeImpl.this.sink = new SinkChannelImpl(Initializer.this.sp, var3);
break;
} var3.close();
var2.close();
}
} catch (IOException var18) {
try {
if (var2 != null) {
var2.close();
} if (var3 != null) {
var3.close();
}
} catch (IOException var17) {
;
} Initializer.this.ioe = var18;
} finally {
try {
if (var1 != null) {
var1.close();
}
} catch (IOException var16) {
;
} } }

在这个run方法中首先定义了三个Channel一个ServerSocketChannel和两个SocketChannel,然后申请了两个十六字节的ByteBuffer缓冲区,定义了一个回送地址var6;在while循环中先检查ServerSocketChannel是否开启了,若没有则需要调用open方法开启并赋值给var1,绑定地址为var6即回送地址,端口为0,令var7这个InetSocketAddress对象的地址是var6,端口是ServerSocketChannel的端口;ServerSocketChannel初始化完毕,初始化一个SocketChannel即var2,通过刚才的var7这个InetSocketAddress对象和ServerSocketChannel建立连接;

在PipeImpl里有一个静态成员:

 private static final Random RANDOM_NUMBER_GENERATOR = new SecureRandom();

RANDOM_NUMBER_GENERATOR 听名字就知道它是用来生成随机数;
通过RANDOM_NUMBER_GENERATOR将从生成的随机数存放在其中一个缓冲区ByteBuffer(var4)中,然后通过刚才连接好的SocketChannel即var2的write方法写入缓冲区中的所有可用数据发送给ServerSocketChannel;令var4缓冲区标志置0;接着ServerSocketChannel调用accept方法侦听刚才的连接产生一个SocketChannel对象var3,从var3中读取数据存放在缓冲区var5中,令var5缓冲区标志置0;然后比较var4和var5中的内容是否一致,若是一致则给PipeImpl的成员source和sink分别初始化保存起来,若不一致就继续循环,不断地重复上述过程,直至Pipe通道成功建立;至此结束LoopbackConnector的run方法。
其在连接建立的过程中若是出现了异常会通过Initializer的ioe成员保存异常。

再回到Initializer的run方法,在完成LoopbackConnector的run方法后,再根据ioe判读是否在刚才的连接建立中出现了ClosedByInterruptException异常,若是出现还需要通过线程启动LoopbackConnector的run方法直至其结束;若不是ClosedByInterruptException异常则直接抛出IOException。

至此PipeImpl的构造结束,再回到WindowsSelectorImpl的构造,通过上述的操作产生的PipeImpl对象就赋值给了wakeupPipe成员;wakeupPipe的source就是刚才产生的SourceChannelImpl对象,wakeupPipe的sink就是刚才产生的SinkChannelImpl对象,再使用wakeupSourceFd保存source的fdVal值和wakeupSinkFd保存sink的fdVal值;并且禁用Nagle算法,最后使用pollWrpper成员保存source的fdVal值。

上述建立的这个连接通道的主要目的不是为了确保能建立连接,而是为了解决Selector的select方法的阻塞问题,调用select方法时只有注册在Selector上的channel有事件就绪时才会被唤醒,而Selector提供的wakeup方法就利用了上述建立好的通道,通过SinkChannel给SourceChannel发送信号量,使得select被唤醒,具体实现会在后续的博客给出。

Selector到此创建完毕。

【Java】NIO中Selector的创建源码分析的更多相关文章

  1. Netty中NioEventLoopGroup的创建源码分析

    NioEventLoopGroup的无参构造: public NioEventLoopGroup() { this(0); } 调用了单参的构造: public NioEventLoopGroup(i ...

  2. 【Java】NIO中Channel的注册源码分析

    Channel的注册是在SelectableChannel中定义的: public abstract SelectionKey register(Selector sel, int ops, Obje ...

  3. RocketMQ中Broker的启动源码分析(二)

    接着上一篇博客  [RocketMQ中Broker的启动源码分析(一)] 在完成准备工作后,调用start方法: public static BrokerController start(Broker ...

  4. RocketMQ中Broker的启动源码分析(一)

    在RocketMQ中,使用BrokerStartup作为启动类,相较于NameServer的启动,Broker作为RocketMQ的核心可复杂得多 [RocketMQ中NameServer的启动源码分 ...

  5. RocketMQ中Broker的消息存储源码分析

    Broker和前面分析过的NameServer类似,需要在Pipeline责任链上通过NettyServerHandler来处理消息 [RocketMQ中NameServer的启动源码分析] 实际上就 ...

  6. JDK中String类的源码分析(二)

    1.startsWith(String prefix, int toffset)方法 包括startsWith(*),endsWith(*)方法,都是调用上述一个方法 public boolean s ...

  7. Springboot中mybatis执行逻辑源码分析

    Springboot中mybatis执行逻辑源码分析 在上一篇springboot整合mybatis源码分析已经讲了我们的Mapper接口,userMapper是通过MapperProxy实现的一个动 ...

  8. RocketMQ中PullConsumer的启动源码分析

    通过DefaultMQPullConsumer作为默认实现,这里的启动过程和Producer很相似,但相比复杂一些 [RocketMQ中Producer的启动源码分析] DefaultMQPullCo ...

  9. Java ThreadPoolExecutor线程池原理及源码分析

    一.源码分析(基于JDK1.6) ThreadExecutorPool是使用最多的线程池组件,了解它的原始资料最好是从从设计者(Doug Lea)的口中知道它的来龙去脉.在Jdk1.6中,Thread ...

随机推荐

  1. linux 系统监控好文

    摘自: http://os.51cto.com/art/201005/200714_all.htm

  2. ubuntu下搭建Scrapy框架简单办法

    1. 先执行以下命令 sudo apt-get install python-lxml sudo apt-get install libxslt1-dev sudo apt-get install p ...

  3. Java -- 键盘输入 Scanner, BufferedReader。 系统相关System,Runtime。随机数 Randrom。日期操作Calendar

    1. Scanner 一个基于正则表达式的文本扫描器,他有多个构造函数,可以从文件,输入流和字符串中解析出基本类型值和字符串值. public class Main { public static v ...

  4. C#操作计划任务

    昨天有一个任务,就是要下载相关文件,然后保存在相关路径下,这个没什么难度,所以就略过不谈,主要谈谈定时下载,即每天某个固定时间执行下载,这个功能我是用C#代码来操作windows自带的任务计划来实现的 ...

  5. the referenced script on this behaviour is missing!

    1.看看你脚本上挂的某个组件是不是发生了变动,比如被删除了什么的 2.最有可能的是你创建完脚本后,中途改过脚本的名字,致使脚本名字和内部的名字不统一.

  6. 机器学习 Support Vector Machines 2

    优化的边界分类器 上一讲里我们介绍了函数边界和几何边界的概念,给定一组训练样本,如果能够找到一条决策边界,能够使得几何边界尽可能地大,这将使分类器可以很可靠地预测训练样本,特别地,这可以让分类器用一个 ...

  7. Arc082_F Sandglass

    Description有一个沙漏由两个上下相通玻璃球$A$和$B$构成,这两个玻璃球都含有一定量的沙子,我们暂且假定$A,B$中位于上方的玻璃球的为$U$,下方的玻璃球为$L$,则除非$U$中没有沙子 ...

  8. 1. Two Sum[LeetCode 简单 by 大志]

    1. 二数之和 题目 English Given an array of integers, return indices of the two numbers such that they add ...

  9. Python:删除字符串中的字符

    一.删除字符串两端的一种或多种字符 #strip().lstrip().rstrip()方法:(默认删除空格符) A.list.strip(字符):删除字符串两端的一种或多种字符: #例:删除字符串s ...

  10. nginx实现带参数目录域名重定向二级域名方法

    本文章介绍了关于nginx实现带参数目录域名重定向二级域名方法,有需要学习的朋友可参考一下. 下面的代码是基于nginx的子目录301到其他域名(URL)的规则.作用是例如访问http://www.p ...