LimitLatch 在 Tomcat 中的应用

开发 开发工具
Tomcat的LimitLatch类用于控制网络通信的socket接收上限,在Tomcat7时引入,实现简单,借此可以学习一下线程同步的相关知识。

Tomcat的LimitLatch类用于控制网络通信的socket接收上限,在Tomcat7时引入,实现简单,借此可以学习一下线程同步的相关知识。

Tomcat

LimitLatch依赖内部类Sync进行线程同步,而Sync继承自大家熟悉的AbstractQueuedSynchronizer。AQS是java.util.concurrent的核心组件,诸多常用的线程同步工具类都能够找到他的影子,读者可以翻阅ReentrantLock、CountDownLatch、Semaphore等类的源码。

  1. //if we have reached max connections, wait 
  2. countUpOrAwaitConnection(); 
  3. SocketChannel socket = null
  4. try { 
  5.       // Accept the next incoming connection from the server socket 
  6.      socket = serverSock.accept(); 
  7.      …… 

不管是NIO还是BIO,Tomcat在接收socket前,都要通过countUpOrAwaitConnection方法获取资源,如果已经达到***连接数,则需要当前线程等待资源释放。该方法最终会调用到LimitLatch的内部类Sync的acquireSharedInterruptibly方法,即AQS的acquireSharedInterruptibly方法。

从内部类Sync的重载方法我们能看到Sync是一个共享模式的同步器,重载了tryAcquireShared和tryReleaseShared两个方法,而两个方法之所以能够如此简单,就是因为父类AQS在背后默默完成了其他所有的排队、等待、激活等一系列逻辑。

  1. protected int tryAcquireShared(int ignored) {            
  2.  long newCount = count.incrementAndGet();         
  3.     if (!released && newCount > limit) {//自增后没有超过资源上限则获取成功                // Limit exceeded                
  4.        count.decrementAndGet();//资源获取失败,回退                
  5.        return -1;         
  6.     } else { 
  7.                 return 1;       
  8.       }  
  9.  } 

在获取共享资源时,LimitLatch.Sync使用了原子变量AtomicLong,利用其自增的CAS原子操作结果与设定的共享资源数量上限进行比较,如果超出上限则目前无法获取资源,由AQS放入等待队列等待下次触发。LimitLatch中定义了released属性,该属性为true时,无论如何都会获取到共享资源。

  1. public boolean releaseAll() {      
  2.    released = true;//标志位置为ture后,后续均可获取资源     
  3.     return sync.releaseShared(0);//通知等待线程重新获取资源   
  4.   } 

这里就有一个问题了,既然无论如何都会获取到资源,LimitLatch就没有存在的必要,那为何还要这样一个看似多余的released 属性呢?这里其实考虑到一个状态变更的问题,当由一个LimitLatch控制资源获取量变更为无需LimitLatch时,仅仅将LimitLatch置为null从而跳过资源竞争是不够的。

如果之前存在在等待队列中等待资源的线程,而此时没有资源释放,那么在状态变更后线程仍然会处于等待状态,这与“***制”的状态是不符的,此时需要将released属性置为true,然后通过一次资源释放由AQS触发所有等待线程重新获取资源,这个时候所有线程均会获取资源立即返回。

  1. protected boolean tryReleaseShared(int arg) {    
  2.          count.decrementAndGet();//自减释放资源      
  3.        return true;     
  4.     } 

资源释放时的代码就更简单了,直接将代表资源的原子变量AtomicLong自减从而释放资源就完成了。而后续的唤醒等待资源的线程等工作已经由AQS代劳了。

写到这里,问题又来了,这个功能完全可以由JDK自带的Semaphore类来完成啊。如果非要再写一个那一定是因为性能的原因了,毕竟该类要使用在接收Socket的前面,对性能有直接影响。下面代码为Semaphore类(JDK1.8)的FairSync重写的tryAcquireShared方法,本质上与LimitLatch并无什么不同,都是CAS自旋:

  1. protected int tryAcquireShared(int acquires) {             
  2. for (;;) {               
  3.  if (hasQueuedPredecessors())             
  4.        return -1;               
  5.  int available = getState();     
  6.            int remaining = available - acquires;     
  7.            if (remaining < 0 ||                    compareAndSetState(available, remaining))     
  8.                return remaining;          
  9.       } 
  10.    } 

话不多说,开始性能测试,测试场景分为64线程竞争64个资源以及64线程竞争32个资源,循环300w次。测试结果竟然是LimitLatch性能要比Semaphore性能低近10%左右,这个。。。一定是我打开的方式不对。

我们回顾一下,当前使用的环境为Windows,JDK版本为1.8,而tomcat7引入LimitLatch的时候正是JDK1.6横行的时代。笔者将JDK切换到1.6进行测试,果然结果变为两者不相上下;而后将测试代码上传到了服务器,在Linux、JDK1.6环境下重新测试,结果LimitLatch扭转乾坤,反超Semaphore性能20%左右,切换到JDK1.8性能领先Semaphore约40%!很明显Tomcat更注重的是Linux服务器下的性能,至于两者性能对比结果在不同环境下不同的原因,欢迎各位大牛一起讨论。

【本文为51CTO专栏作者“侯树成”的原创稿件,转载请通过作者微信公众号『Tomcat那些事儿』获取授权】

戳这里,看该作者更多好文

责任编辑:赵宁宁 来源: 51CTO专栏
相关推荐

2019-08-15 16:30:49

TomcatSpringBootJava

2009-06-05 14:59:31

Eclipse中配置T

2019-12-09 15:08:30

JavaTomcatWeb

2020-05-22 10:40:33

ContinuatioJS前端

2009-02-27 16:22:34

AjaxProAjax.NET

2023-03-24 09:07:22

SignalsJavaScript应用

2010-06-02 13:05:52

tomcat和svn

2011-06-23 09:14:52

CRM云计算

2009-02-03 10:19:45

2009-06-29 17:09:49

JavaBeanJSP

2020-05-06 07:53:09

物联网物流IOT

2010-10-08 10:15:34

IFrameJS控件

2009-06-25 15:54:18

设计模式EJB

2010-08-03 11:07:34

NFSVMware快照

2022-06-30 20:47:58

区块链

2010-08-09 10:21:56

XMLFlex

2022-06-28 08:02:44

SPISpringJava

2022-10-26 07:47:54

2022-06-30 08:58:09

时钟轮RPC框架

2014-08-08 16:50:21

AB 测试安卓推送
点赞
收藏

51CTO技术栈公众号