不同操作系统对 java.nio.channels.Selector
的实现存在显著差异,这主要源于它们底层 I/O 多路复用机制的不同。
- Windows:
SelectorProvider
实现类:WindowsSelectorProvider。Selector
实现类:WindowsSelectorImpl。- C++ native 实现目录。
- Linux:
SelectorProvider
实现类:EpollSelectorProvider。Selector
实现类:EPollSelectorImpl。- C++ native 实现目录。
- MacOS/BSD:
SelectorProvider
实现类:KQueueSelectorProvider。Selector
实现类:KQueueSelectorImpl。- C++ native 实现目录。
我们以 Linux 系统为例。
Java NIO 程序中,通常会调用 SelectorProvider.provider().openSelector()
获取多路复用器 Selector
。
Linux 系统中,SelectorProvider 的静态方法 provider()
会返回 EPollSelectorProvider 对象。
package java.nio.channels.spi;
...
public abstract class SelectorProvider {
...
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()
方法的逻辑, 依次调用 3 个方法去获取 SelectorProvider
对象,直到首次获取成功后返回。
loadProviderFromProperty()
loadProviderAsService()
sun.nio.ch.DefaultSelectorProvider.create()
loadProviderFromProperty()
loadProviderFromProperty()
加载系统属性 -Djava.nio.channels.spi.SelectorProvider=XXX
指定的 SelectorProvider
实现类。这种方式优先级最高。
private static boolean loadProviderFromProperty() {
// 系统属性 "java.nio.channels.spi.SelectorProvider"
String cn = System.getProperty("java.nio.channels.spi.SelectorProvider");
if (cn == null)
return false;
try {
// 加载 SelectorProvider 实现类,并初始化
Class<?> c = Class.forName(cn, true,
ClassLoader.getSystemClassLoader());
// 创建 SelectorProvider 对象
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);
}
}
loadProviderAsService()
SelectorProvider
作为服务,由服务加载器 ServiceLoader
从 META-INF/services/java.nio.channels.spi.SelectorProvider
文件中加载 SelectorProvider
实现类。
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.load()
会创建一个 LazyIterator
懒加载迭代器对象。顾名思义,只有在迭代时才会到 META-INF/services/java.nio.channels.spi.SelectorProvider
中查找并加载 SelectorProvider
实现类。
i.hasNext()
i.hasNext()
会委托 LazyIterator
对象去 META-INF/services/java.nio.channels.spi.SelectorProvider
中查找 SelectorProvider
实现类。
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
knownProviders
被用作缓存,加快查找速度。如果缓存中有,直接使用缓存中的数据。
lookupIterator
字段保存的就是 LazyIterator
对象。
lookupIterator.hasNext()
代码如下:
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
实际调用 hasNextService()
完成处理.
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
....
}
PREFIX
字段保存字符串 "META-INF/services/"
。而 service
字段保存的是 java.nio.channels.spi.SelectorProvider
的 Class 对象。因此 fullName
就是 META-INF/services/java.nio.channels.spi.SelectorProvider
。fullName
文件中保存了 SelectorProvider
实现类的全限定名称。将实现类全限定名字符串加载到内存中。
i.next()
知道了类的全限定名就可以加载该类了。这是在通过 i.next()
完成的。
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
i.next()
会委托给 LazyIterator
对象的 lookupIterator.next()
方法加载类。
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
next()
方法调用 nextService()
完成主要逻辑。
private S nextService() {
....
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
...
}
}
sun.nio.ch.DefaultSelectorProvider.create()
我来看看 Linux 对 sun.nio.ch.DefaultSelectorProvider.create() 的具体实现。
public static SelectorProvider create() {
String osname = AccessController
.doPrivileged(new GetPropertyAction("os.name"));
if (osname.equals("SunOS"))
return createProvider("sun.nio.ch.DevPollSelectorProvider");
if (osname.equals("Linux"))
return createProvider("sun.nio.ch.EPollSelectorProvider");
return new sun.nio.ch.PollSelectorProvider();
}
直接硬编码类名 "sun.nio.ch.EPollSelectorProvider"
。可见,这种方式是兜底的。只有前面两种动态加载的方式都不起作用时,才会调用该方法。
EPollSelectorProvider 源码如下:
package sun.nio.ch;
import java.io.IOException;
import java.nio.channels.*;
import java.nio.channels.spi.*;
public class EPollSelectorProvider
extends SelectorProviderImpl
{
public AbstractSelector openSelector() throws IOException {
return new EPollSelectorImpl(this);
}
public Channel inheritedChannel() throws IOException {
return InheritedChannel.getChannel();
}
}
sun.nio.ch.EPollSelectorProvider
继承自 sun.nio.ch.SelectorProviderImpl
,并重写了 openSelector()
方法。
EPollSelectorProvider
有 3 个主要方法:
-
openSelector()
:在EPollSelectorProvider
中进行了重写。返回EPollSelectorImpl
对象。public AbstractSelector openSelector() throws IOException { return new EPollSelectorImpl(this); }
-
openServerSocketChannel()
:在父类SelectorProviderImpl
中实现。服务端使用该方法获得一个ServerSocketChannel
对象。public ServerSocketChannel openServerSocketChannel() throws IOException { return new ServerSocketChannelImpl(this); }
-
openSocketChannel()
:在父类SelectorProviderImpl
中实现。客户端使用该方法获得一个SocketChannel
对象public SocketChannel openSocketChannel() throws IOException { return new SocketChannelImpl(this); }