BlockingQueue使用
def startServer(bindAddress: String, port: Int): Unit = {
val bootstraps: java.util.List[TransportServerBootstrap] =
if (securityManager.isAuthenticationEnabled()) {
java.util.Arrays.asList(new AuthServerBootstrap(transportConf, securityManager))
} else {
java.util.Collections.emptyList()
}
server = transportContext.createServer(bindAddress, port, bootstraps)
dispatcher.registerRpcEndpoint(
RpcEndpointVerifier.NAME, new RpcEndpointVerifier(this, dispatcher))
}
在Spark中的Master或者Worker启动的时候会创建RpcEnv,启动server之后会调用dispatcher.registerRpcEndpoint方法。
def registerRpcEndpoint(name: String, endpoint: RpcEndpoint): NettyRpcEndpointRef = {
val addr = RpcEndpointAddress(nettyEnv.address, name)
val endpointRef = new NettyRpcEndpointRef(nettyEnv.conf, addr, nettyEnv)
synchronized {
if (stopped) {
throw new IllegalStateException("RpcEnv has been stopped")
}
if (endpoints.putIfAbsent(name, new EndpointData(name, endpoint, endpointRef)) != null) {
throw new IllegalArgumentException(s"There is already an RpcEndpoint called $name")
}
val data = endpoints.get(name)
endpointRefs.put(data.endpoint, data.ref)
receivers.offer(data) // for the OnStart message
}
endpointRef
}
在这个方法中,会调用receiver.offer(data)方法。receiver就是一个阻塞队列。
// Track the receivers whose inboxes may contain messages.
private val receivers = new LinkedBlockingQueue[EndpointData]
一旦receivers中有数据,立即唤醒MessageLoop线程进行数据的异步处理。
/** Message loop used for dispatching messages. */
private class MessageLoop extends Runnable {
override def run(): Unit = {
try {
while (true) {
try {
val data = receivers.take()
if (data == PoisonPill) {
// Put PoisonPill back so that other MessageLoops can see it.
receivers.offer(PoisonPill)
return
}
data.inbox.process(Dispatcher.this)
} catch {
case NonFatal(e) => logError(e.getMessage, e)
}
}
java.util.concurrent.LinkedBlockingQueue#take方法调用,如果队列中没有任何数据,那么将会将线程无限期挂起,直到有队列中添加了数据。
而Inbox初始化的时候就会向messages中添加OnStart。
// OnStart should be the first message to process
inbox.synchronized {
messages.add(OnStart)
}
所以服务启动就会执行endponti的start方法。
从以上的案例,可以看出,阻塞队列有两个重要方法,一个是offer,一个是take,offer就是往队列中加入数据,take则是取数据,如果取不到则先挂起,直到有数据。接下来分析,为啥没有数据,take方法将无限挂起。而offer方法又是如何唤醒挂起线程的。
最终会调用
LockSupport.unpark方法唤醒线程。
接下来看下take是如何阻塞线程的。
最终调用了 LockSupport.park(this);
方法,阻塞当前线程。