ダボサービスコール
私はこれまで十数記事を書いており、ダボの操作についてはある程度理解しています。Dubbo サービス呼び出しが最も重要であり、このプロセスを視覚的に記述するには少なくとも 5 ~ 6 つの記事が必要です。
サーバー側での Netty のハンドラー パッケージ化
サービス エクスポートのプロセスでは、サービス プロバイダーの情報を URL に変換して登録センターに配置すること、サーバーを起動すること、および netty 処理リクエストのデータ プロセスに依存する 2 つの処理が実行されます。各ハンドラーに依存するため、リクエストを処理するハンドラーを理解する必要があります。
private ExchangeServer createServer(URL url) {
//省略部分代码
ExchangeServer server;
try {
// requestHandler是请求处理器,类型为ExchangeHandler
// 表示从url的端口接收到请求后,requestHandler来进行处理
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
}
return server;
}
まず、requestHandler が渡されます。このハンドラーは ExchangeHandlerAdapter クラスです。
Exchangeハンドラーアダプター
リクエストが到着すると、まず受信(チャネル チャネル、オブジェクト メッセージ)を呼び出し、次に応答(ExchangeChannel チャネル、オブジェクト メッセージ)を呼び出してリクエストを処理します。メッセージは要求されたデータであり、チャネルはクライアントとの長い接続を表します。 ; 作業
プロセス:最後に、Method.invoke を呼び出して、リフレクション テクノロジを通じてサービスを実行します。
- 型変換、オブジェクトを呼び出しに変換します。
- getInvoker を呼び出してサービス プロバイダーのエグゼキューターを取得します (このインボーカーは複数のレイヤーでラップされます)。
- リモートアドレスremoteAddressをRpcContextに設定します
- エグゼキューターの invoke メソッドを呼び出して結果を返します。
- CompletionFuture インスタンスを返します。
private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
@Override
public CompletableFuture<Object> reply(ExchangeChannel channel, Object message) throws RemotingException {
Invocation inv = (Invocation) message;
Invoker<?> invoker = getInvoker(channel, inv);
//省略部分代码
RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
Result result = invoker.invoke(inv);
return result.completionFuture().thenApply(Function.identity());
}
@Override
public void received(Channel channel, Object message) throws RemotingException {
if (message instanceof Invocation) {
// 这是服务端接收到Invocation时的处理逻辑
reply((ExchangeChannel) channel, message);
} else {
super.received(channel, message);
}
}
}
Exchangers.bind(url, requestHandler)
プロセス:
- getExchanger(url) を呼び出して、SPI メカニズムを通じて Exchanger の拡張実装クラスを取得します。デフォルトの実装クラスは HeaderExhanger です。
- binding メソッドを呼び出して netty を開始します。
public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
// codec表示协议编码方式
url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
// 通过url得到HeaderExchanger, 利用HeaderExchanger进行bind,将得到一个HeaderExchangeServer
return getExchanger(url).bind(url, handler);
}
HeaderExchanger#bind(URL, ハンドラー)
仕事:
- まず、受信した ExchangeHandlerAdapter インスタンス ハンドラーが HeaderExchangerHandler としてパッケージ化されます。
- 次に、HeaderExchangerHandler を DecodeHandler のインスタンスとしてラップします。
- Transporters#bind メソッドを呼び出して NettyServer を作成します。
- NettyServer インスタンスを HeaderExchangerServer としてラップします。
@Override
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
// 下面会去启动Netty
// 对handler包装了两层,表示当处理一个请求时,每层Handler负责不同的处理逻辑
// 为什么在connect和bind时都是DecodeHandler,解码,解的是把InputStream解析成RpcInvocation对象
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
Transporters.bind(url, ハンドラー)
作業過程:
- 複数のハンドラーがバインドされている場合、接続が到着すると、各ハンドラーが循環して接続を処理します。
- getTransport() メソッドを呼び出して、SPI メカニズムを通じて Transporter インスタンスを取得します。これは、デフォルトでは NettyTransporter インスタンスです。
- NettyTransport#bind メソッドを呼び出して nettyServer を作成します。
public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
//....
//
ChannelHandler handler;
if (handlers.length == 1) {
handler = handlers[0];
} else {
handler = new ChannelHandlerDispatcher(handlers);
}
// 调用NettyTransporter去绑定,Transporter表示网络传输层
return getTransporter().bind(url, handler);
}
NettyTransporter#bind(url, リスナー)
Nettyserver インスタンスを作成します。
public class NettyTransporter implements Transporter {
public static final String NAME = "netty";
@Override
public Server bind(URL url, ChannelHandler listener) throws RemotingException {
return new NettyServer(url, listener);
}
@Override
public Client connect(URL url, ChannelHandler listener) throws RemotingException {
return new NettyClient(url, listener);
}
}
NettyServer(URL URL、ChannelHandler ハンドラー)
- ChannelHandlers.wrap メソッドを呼び出して DecoderHandler インスタンスをラップします。
- 親クラスのコンストラクターを呼び出します。
public class NettyServer extends AbstractServer implements Server {
private Map<String, Channel> channels;
private ServerBootstrap bootstrap;
private io.netty.channel.Channel channel;
private EventLoopGroup bossGroup;
private EventLoopGroup workerGroup;
public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
}
ChannelHandlers.wrap(ChannelHandler ハンドラー、URL URL)
- ChannelHandlers#getInstance() メソッドを呼び出して、空腹の中国スタイルを使用してシングルトン ChannelHandlers を取得します。
- WrapInternal(handler, url) を呼び出して、DecoderHandler インスタンス ハンドラーをラップします。
public static ChannelHandler wrap(ChannelHandler handler, URL url) {
return ChannelHandlers.getInstance().wrapInternal(handler, url);
}
ChannelHandlers#wrapInternal(ハンドラー, URL)
作業過程:
- Spi メカニズムを通じて DecoderHandler インスタンスを AllChannelHandler インスタンスとして呼び出します。
- 次に、AllChannelHandler を MultiMessageHandler インスタンスとしてラップします。
public class ChannelHandlers {
// 单例模式
private static ChannelHandlers INSTANCE = new ChannelHandlers();
protected ChannelHandlers() {
}
public static ChannelHandler wrap(ChannelHandler handler, URL url) {
return ChannelHandlers.getInstance().wrapInternal(handler, url);
}
protected static ChannelHandlers getInstance() {
return INSTANCE;
}
static void setTestingChannelHandlers(ChannelHandlers instance) {
INSTANCE = instance;
}
protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
// 先通过ExtensionLoader.getExtensionLoader(Dispatcher.class).getAdaptiveExtension().dispatch(handler, url)
// 得到一个AllChannelHandler(handler, url)
// 然后把AllChannelHandler包装成HeartbeatHandler,HeartbeatHandler包装成MultiMessageHandler
// 所以当Netty接收到一个数据时,会经历MultiMessageHandler--->HeartbeatHandler---->AllChannelHandler
// 而AllChannelHandler会调用handler
return new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class)
.getAdaptiveExtension().dispatch(handler, url)));
}
}
ここで、Dispatter にはサーバーのスレッド モデルが関係します。
AbstractServer(URL URL、ChannelHandler ハンドラー)
NettyServer インスタンスを作成すると、親クラス コンストラクター、親クラス AbstractServer、抽象クラスが呼び出されます。
ワークフロー:
- 親クラスのコンストラクターを呼び出し、ハンドラーを親クラス AbstractPeer の handler 属性に割り当てます。
- ローカル アドレス localAddress を取得します。
- サービスにバインドされた IP を取得します。
- サービスにバインドされたポート番号を取得します。
- InetSocketAddress のインスタンスを作成します。これは、ソケット接続の構築パラメータの作成に使用されます。
- doOpen() を呼び出して netty を開始します。
public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
super(url, handler);
localAddress = getUrl().toInetSocketAddress();
String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
if (url.getParameter(ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {
bindIp = ANYHOST_VALUE;
}
bindAddress = new InetSocketAddress(bindIp, bindPort);
this.accepts = url.getParameter(ACCEPTS_KEY, DEFAULT_ACCEPTS);
this.idleTimeout = url.getParameter(IDLE_TIMEOUT_KEY, DEFAULT_IDLE_TIMEOUT);
try {
doOpen();
if (logger.isInfoEnabled()) {
logger.info("Start " + getClass().getSimpleName() + " bind " + getBindAddress() + ", export " + getLocalAddress());
}
} catch (Throwable t) {
//....
}
//...
}
AbstractEndpoint(url, ハンドラー)
親クラスのコンストラクターを呼び出し、エンコード方式を設定し、サービスプロバイダーのタイムアウト期間を設定し、接続を作成するためのタイムアウト期間を設定します。
public AbstractEndpoint(URL url, ChannelHandler handler) {
super(url, handler);
this.codec = getChannelCodec(url);
this.timeout = url.getPositiveParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT);
this.connectTimeout = url.getPositiveParameter(Constants.CONNECT_TIMEOUT_KEY, Constants.DEFAULT_CONNECT_TIMEOUT);
}
AbstractPeer(URL url、ChannelHandler ハンドラー)
ハンドラーを保存します。
public abstract class AbstractPeer implements Endpoint, ChannelHandler {
private final ChannelHandler handler;
private volatile URL url;
private volatile boolean closing;
private volatile boolean closed;
public AbstractPeer(URL url, ChannelHandler handler) {
this.url = url;
this.handler = handler;
}
NettyServer#doOpen()
上記のプロセスでは、新しい NettyServer を呼び出すと、NettyServer の Handler プロパティに MultiMessageHandler が割り当てられます。
したがって、NettyServer の別の ID は MultiMessageHandler:
ワークフローです。
- ServerBootstrap サーバーを作成します。
- ワーカー スレッド グループ、IO イベント スレッド グループを作成します。
- NettyServerHandler を作成し、これを渡します。つまり、MultiMessageHandler を渡し、NettyServerHandler インスタンスとしてパッケージ化します。
- サーバーパラメータの設定。
@Override
protected void doOpen() throws Throwable {
bootstrap = new ServerBootstrap();
bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("NettyServerBoss", true));
workerGroup = new NioEventLoopGroup(getUrl().getPositiveParameter(IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),
new DefaultThreadFactory("NettyServerWorker", true));
final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
channels = nettyServerHandler.getChannels();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
.childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
// FIXME: should we use getTimeout()?
int idleTimeout = UrlUtils.getIdleTimeout(getUrl());
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug
.addLast("decoder", adapter.getDecoder())
.addLast("encoder", adapter.getEncoder())
.addLast("server-idle-handler", new IdleStateHandler(0, 0, idleTimeout, MILLISECONDS))
.addLast("handler", nettyServerHandler);
}
});
// bind
ChannelFuture channelFuture = bootstrap.bind(getBindAddress());
channelFuture.syncUninterruptibly();
channel = channelFuture.channel();
}
サーバー側での Netty のハンドラー パッケージ化プロセスの概要
- ExchangeHandlerAdapter の requestHandler インスタンスは HeaderExchangerhandler としてラップされます
- HandlerExchangerHandler インスタンスを Decodehandler としてラップします。
- DecodeHandler インスタンスを AllChannelHandler としてラップします。
- AllChannelHandler インスタンスを MultiMessageHandler インスタンスとしてラップします。
- MultiMessageHandler インスタンスを NettyServerHandler としてラップします。
- 最後に、NettyServerHandler を PipeLine のハンドラー プロセッサにバインドします。
サーバー側のスレッドモデル
関係するプロセスは次のとおりです。DecodeHandler を AllChannelhandler としてラップするプロセス。
protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
return new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class)
.getAdaptiveExtension().dispatch(handler, url)));
}
SPI機構を通じてDispatcherの拡張実装クラスを取得し、
@SPI(AllDispatcher.NAME)
public interface Dispatcher {
/**
* dispatch the message to threadpool.
*
* @param handler
* @param url
* @return channel handler
*/
@Adaptive({
Constants.DISPATCHER_KEY, "dispather", "channel.handler"})
// The last two parameters are reserved for compatibility with the old configuration
ChannelHandler dispatch(ChannelHandler handler, URL url);
}
デフォルトでは、AllDispatcher#dispatch メソッドが使用されます。
AllDispatcher #dispatch(ハンドラー, URL)
AllChannelHandler のインスタンスを作成し、ハンドラーをラップします。
public class AllDispatcher implements Dispatcher {
public static final String NAME = "all";
@Override
public ChannelHandler dispatch(ChannelHandler handler, URL url) {
return new AllChannelHandler(handler, url);
}
}
すべてのチャネルハンドラー
netty はデータを受信すると、受信したメソッドを呼び出します。
public class AllChannelHandler extends WrappedChannelHandler {
public AllChannelHandler(ChannelHandler handler, URL url) {
// 会生成一个线程池
super(handler, url);
}
//连接完成处理
@Override
public void connected(Channel channel) throws RemotingException {
ExecutorService executor = getExecutorService();
try {
executor.execute(new ChannelEventRunnable(channel, handler, ChannelState.CONNECTED));
} catch (Throwable t) {
throw new ExecutionException("connect event", channel, getClass() + " error when process connected event .", t);
}
}
//连接断开处理
@Override
public void disconnected(Channel channel) throws RemotingException {
ExecutorService executor = getExecutorService();
try {
executor.execute(new ChannelEventRunnable(channel, handler, ChannelState.DISCONNECTED));
} catch (Throwable t) {
throw new ExecutionException("disconnect event", channel, getClass() + " error when process disconnected event .", t);
}
}
@Override
public void received(Channel channel, Object message) throws RemotingException {
ExecutorService executor = getExecutorService();
try {
// 交给线程池去处理message
executor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));
} catch (Throwable t) {
//TODO A temporary solution to the problem that the exception information can not be sent to the opposite end after the thread pool is full. Need a refactoring
//fix The thread pool is full, refuses to call, does not return, and causes the consumer to wait for time out
if(message instanceof Request && t instanceof RejectedExecutionException){
Request request = (Request)message;
if(request.isTwoWay()){
String msg = "Server side(" + url.getIp() + "," + url.getPort() + ") threadpool is exhausted ,detail msg:" + t.getMessage();
Response response = new Response(request.getId(), request.getVersion());
response.setStatus(Response.SERVER_THREADPOOL_EXHAUSTED_ERROR);
response.setErrorMessage(msg);
channel.send(response);
return;
}
}
throw new ExecutionException(message, channel, getClass() + " error when process received event .", t);
}
}
//异常处理;
@Override
public void caught(Channel channel, Throwable exception) throws RemotingException {
ExecutorService executor = getExecutorService();
try {
executor.execute(new ChannelEventRunnable(channel, handler, ChannelState.CAUGHT, exception));
} catch (Throwable t) {
throw new ExecutionException("caught event", channel, getClass() + " error when process caught event .", t);
}
}
}
ラップされたチャネルハンドラ
このクラスは AllChannelHandler の親クラスであり、
構築メソッドのワークフローは次のとおりです。
- DecodeHandler インスタンスを属性ハンドラーに渡します。
- SPI メカニズムを通じて、固定スレッド プールを取得します。使用される SPI スレッド プールのデフォルトの拡張クラスは、FixedThreadPool です。
- componentKey の値を決定します。コンシューマの場合、componentKey はコンシューマ、サービス プロバイダの場合、java.util.concurrent.ExecutorService です。
- SPI メカニズムを通じてデータストアを取得します。SimpleDataStore#put メソッドが使用されます。内部構造は、2 層の Map 構造である Map<String, Map> です。最初の層の Map のキーは、componentKey で、2 番目の層の Map は、componentKey です。レイヤー マップ キーはサービスのポート番号、値は 2 によって作成されたスレッド プールです。
public class WrappedChannelHandler implements ChannelHandlerDelegate {
protected static final Logger logger = LoggerFactory.getLogger(WrappedChannelHandler.class);
protected static final ExecutorService SHARED_EXECUTOR = Executors.newCachedThreadPool(new NamedThreadFactory("DubboSharedHandler", true));
protected final ExecutorService executor;
protected final ChannelHandler handler;
protected final URL url;
public WrappedChannelHandler(ChannelHandler handler, URL url) {
this.handler = handler;
this.url = url;
executor = (ExecutorService) ExtensionLoader.getExtensionLoader(ThreadPool.class).getAdaptiveExtension().getExecutor(url);
String componentKey = Constants.EXECUTOR_SERVICE_COMPONENT_KEY;
if (CONSUMER_SIDE.equalsIgnoreCase(url.getParameter(SIDE_KEY))) {
componentKey = CONSUMER_SIDE;
}
// DataStore底层就是一个map,存储的格式是这样的:{"java.util.concurrent.ExecutorService":{"20880":executor}}
// 这里记录了干嘛?应该是在请求处理的时候会用到
DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
dataStore.put(componentKey, Integer.toString(url.getPort()), executor);
}
}
固定スレッドプール
スレッド プールを作成します。
public class FixedThreadPool implements ThreadPool {
@Override
public Executor getExecutor(URL url) {
String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);
int threads = url.getParameter(THREADS_KEY, DEFAULT_THREADS);
int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES);
return new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS,
queues == 0 ? new SynchronousQueue<Runnable>() :
(queues < 0 ? new LinkedBlockingQueue<Runnable>()
: new LinkedBlockingQueue<Runnable>(queues)),
new NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
}
}
シンプルデータストア
DataStore 内には、Map<String,Map> 構造であるデータ属性があります。
public class SimpleDataStore implements DataStore {
// <component name or id, <data-name, data-value>>
private ConcurrentMap<String, ConcurrentMap<String, Object>> data =
new ConcurrentHashMap<String, ConcurrentMap<String, Object>>();
@Override
public Map<String, Object> get(String componentName) {
ConcurrentMap<String, Object> value = data.get(componentName);
if (value == null) {
return new HashMap<String, Object>();
}
return new HashMap<String, Object>(value);
}
@Override
public Object get(String componentName, String key) {
if (!data.containsKey(componentName)) {
return null;
}
return data.get(componentName).get(key);
}
@Override
public void put(String componentName, String key, Object value) {
Map<String, Object> componentData = data.get(componentName);
if (null == componentData) {
data.putIfAbsent(componentName, new ConcurrentHashMap<String, Object>());
componentData = data.get(componentName);
}
componentData.put(key, value);
}
@Override
public void remove(String componentName, String key) {
if (!data.containsKey(componentName)) {
return;
}
data.get(componentName).remove(key);
}
}
Dubbo に関係するスレッド プール
サーバーとして、いくつかのスレッド プールがあります
- Netty は、接続イベント サービス スレッド プール BossGroup の処理を担当します。
- Netty は、読み取りおよび書き込みイベントのワーカー スレッド プール workerGroup を処理する責任があります。
- 業務処理を担当する業務スレッドプール、つまりAllChannelHandlerに対応するスレッドプール。
Dubbo のスレッドモデル
- acceptイベントの場合はNettyのBossGroupサービススレッドプールで処理され、処理後ワークスレッドプールworkGroup中間ワーカーにチャネル情報が登録されます
- 読み取り/書き込みイベントの場合、Netty の WorkerGroup ワーカー スレッド プールによって処理されます。Netty の WorkerGroup ワーカー スレッド プールは、ネットワーク IO リクエストの処理を担当するため、Dubbo の IO スレッド プールになります。
- ワーカー スレッド プールは、AllChannelHandler の受信データ イベント処理を呼び出し、現在のチャネル、時間タイプ、リクエスト データ パラメーターを使用して channelEventRunnable のインスタンスを作成し、それを処理のためにビジネス スレッド プールに渡します。ワーカー スレッドはプロセスに戻ります。その他の読み取りおよび書き込みイベント。
- このようにリクエストを処理するためにビジネス スレッド プールを作成するのはなぜでしょうか?
- ワーカー スレッド プールのスレッド数は一般に CPU コア数 + 1 であるため、IO スレッドを使用してビジネスを処理する場合、各 IO スレッドが時間のかかるビジネスを実行すると、サーバー/コンシューマー全体が混乱してしまいます。ブロック状態、短い サービスは一定時間利用できません。したがって、IO スレッドのビジネス処理は処理のために Dubbo のビジネス スレッド プールに引き渡され、IO スレッドは新しい読み取りおよび書き込み要求イベントの処理に直接戻ります。これにより、システムのパフォーマンスが向上します。したがって、Dubbo では、スレッド モデルは指定されません。通常は All タイプのスレッド モデルが使用されます。つまり、ビジネス スレッド プールを作成するために AllchennelHandler が作成されます。
Dubbo のその他のねじ切りモデル
- すべて
リクエスト、応答、接続イベント、切断イベント、ハートビートなどを含むすべてのメッセージがスレッド プールにディスパッチされます。 - direct
すべてのメッセージは IO スレッドで直接処理されます。つまり、IO スレッドはビジネスの処理に使用されます。 - 要求
応答メッセージのみがスレッド プールにディスパッチされ、その他の接続切断イベント、ハートビート、その他のメッセージは IO スレッドで直接実行されます。 - 実行は、
応答、応答およびその他の接続切断イベント、ハートビートおよびその他のメッセージを除き、リクエスト メッセージのみをスレッド プールに送信し、IO スレッド上で直接実行されます。 - 接続
は IO スレッド上で行われ、接続切断イベントをキューに入れ、順番に 1 つずつ実行し、他のメッセージをスレッド プールにディスパッチします。
対応する集中スレッド プール: すべては ThreadPool インターフェイスの実装クラスです。
- 固定
固定サイズのスレッド プールでは、スレッドは起動時に作成され、閉じられず、常に保持されます。
これに応じて、FixedThreadPool によって作成されたスレッド プールが使用されます。 - キャッシュされた
スレッド プールは、1 分間アイドル状態になると自動的に削除され、必要に応じて再構築され、それに応じて
CachedThreadPool によって作成されたスレッド プールが使用されます。 - スケーラブルなスレッド プールには制限があります
が、プール内のスレッドの数は増加するだけで、減少しません。縮小せずに拡大のみを行うのは、縮小時に突然大量のトラフィックが発生することによって発生するパフォーマンスの問題を回避するためです。
これに応じて、LimitedThreadPool によって作成されたスレッド プールが使用されます。 - Eager
はワーカー スレッド プールの作成を優先します。タスクの数が corePoolSize より大きく、maximumPoolSize より小さい場合、タスクを処理するためにワーカーが最初に作成されます。タスクの数がmaximumPoolSizeより大きい場合、タスクをブロッキングキューに入れます。ブロッキング キューがいっぱいの場合は、RejectedExecutionException をスローします。(cached と比較して、cached は、タスクの数が MaximumPoolSize を超えると、タスクをブロッキング キューに入れるのではなく、直接例外をスローします)
EagertThreadPool によって作成された対応するスレッド プールが使用されます