前面几篇文章,分别介绍和并发的生活化的概念以及与之对应的Java语言中的实现。
大白话描述并发编程重要概念
Java并发编程相关概念及注意事项
这一篇,我们来看看,对于这些概念,Tomcat内部是如何实践的,以及Tomcat容器内是如何使用并发的。
首先是最常用的synchronized
在容器的启动流程中,会从Server开始一直向到各个下层容器进行启动,下面的代码是Server找到配置的Service,进行遍历启动
private final Object servicesLock = new Object();
// Start our defined Services
synchronized (servicesLock) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
service的启动过程,其实是这样的
protected void startInternal() throws LifecycleException {
// Start our defined Container first
if (container != null) {
synchronized (container) {
container.start();
}
}
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
}
上面的代码,我们看到,并不是整个方法进行加锁,而是对于各个容器内组件的启动进行 分别加锁 。这种对于锁作用范围和持有时间的缩小,可以降低锁竞争,提升可伸缩性。当然,如果说所有的内容都分别加锁反而会影响性能。感兴趣的朋友可以阅读 Java并发编程实战 的性能与可伸缩性一章,了解更多内容。
// Start our child containers, if any
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (int i = 0; i < children.length; i++) {
results.add( startStopExecutor.submit (new StartChild(children[i])));
}
boolean fail = false;
for (Future<Void> result : results) {
try {
result.get();
} catch (Exception e) {
} }
各个容器获取到其子组件后,将其组装成一个任务,提交到任务执行线程池中。任务的执行结果,在通过其 Future 对象获取。
private static class StartChild implements Callable< Void > {
private Container child;
public StartChild(Container child) {
this.child = child;
}
public Void call() throws LifecycleException {
child.start();
return null;
} }
由于组件的启动并不需要返回值,此处使用Void类型,可以在实际使用过程中换成具体的值返回具体的结果。在全部任务执行完成后,从Future中get返回值。
Volatile的使用
private volatile boolean close = false ;
// Time to terminate?
if ( close ) {
timeout(0, false);
try {
selector.close();
} catch (IOException ioe) {
}
通过使用volatile值,来保证多线程环境中关闭标识的可见性,从而能正确的在标识改变后退出特定逻辑。
在前面的概念中,我们提到使用wait/notify的时候,一定要在拿到锁的情况下进行。Tomcat在进行Servlet实例allocate和deallocate的时候,会使用到这两个操作。
synchronized (instancePool) {
while (countAllocated.get() >= nInstances) {
if (nInstances < maxInstances) {
instancePool.push(loadServlet());
nInstances++;
} else {
instancePool.wait();
}
}
卸载的时候,代码是这样的
synchronized (instancePool) {
countAllocated.decrementAndGet();
instancePool.push(servlet);
instancePool.notify();
}
两种情况都是先拿到锁再进行的。
当然,Tomcat中也有许多对JDK并发包内组件的使用,像下面这个对于CountDownLatch的使用
private volatile CountDownLatch stopLatch = null;
stopLatch = new CountDownLatch(pollerThreadCount); // 在Endpoint进行bind操作时,设置相应poller数量的CountDownLatch
// 在处理destory时,进行countDown操作,后续的关闭操作,会根据stopLatch的数据进行等待操作。
stopLatch.countDown();
以上,是Tomcat内部并发技术的使用方式,对于并发包内组件的使用这一部分,后面具体介绍相关内容时再细说。
Java并发编程相关概念及注意事项
大白话描述并发编程重要概念
来看看你貌似熟悉的单例模式
线程池的原理
Tomcat的Connector组件
深度揭秘乱码问题背后的原因及解决方式
WEB应用是怎么被部署的?
怎样调试Tomcat源码
IDE里的Tomcat是这样工作的!
重定向与转发的本质区别
怎样阅读源代码
本公众号由曾从事应用服务器核心研发的工程师维护。文章深入Tomcat源码,分析应用服务器的实现细节,工作原理及与之相关的技术,使用技巧,工作实战等。起于Tomcat但不止于此。同时会分享并发、JVM等,内容多为原创,欢迎关注。
扫描或长按下方二维码,即可关注!