Google Java Style 中文翻译

Google Java Style 终于在前不久发布了,抽空学习了一下,同时生成了一个java代码注释风格的中文解释,如果你有兴趣可以看看。

java source code 在此: github下载

代码中的注释基本上已经把Google Java Style中提到的一些要求写进去了,而且是出现在该出现的地方。主要是缩进的地方我跟google要求的2个字符的缩进没有保持一致, Google的这篇文档中要求的是2个字符,而我个人一直以来都是习惯使用tab来缩进,所以这个恕难从命哈。

基本上跟以前的java编码习惯差不多,就把一些Java 7之后的新的语法特性也提到了,更之前的广为流传的Sun的Java Style区别不大。如果你之前是跟Sun的保持一致的,基本上风格上不需要大改的。

其实编码风格无所谓好坏,最重要的是能让别人更容易的读懂你的代码,这并不是说每一行代码都要写注释,注释不能解决一切问题,更重要的是各种变量和名称的命名规范,这个很重要。如果代码本身就能自解释,那才是极好的!

用mongodb构建延时队列

延时队列(DelayQueue)的使用场景有很多,比如订单类的系统,用户创建订单后一段时间内如果没有付款,那么要把用户的这个订单关闭掉,同时把库存还原回去。解决的方案有很多,一种是用定时任务,定时去扫描符合条件的数据出来进行出来,还有一种就是把这个丢到延时队列里面,等时间到了自动出列之后处理。

由于我们业务场景需要实时的延时队列,也就是必须准时处理,如果通过定时扫描的话,如果时间间隔短,会任务太多处理不过来,如果时间间隔长,会导致中间的有一些延时出列了。之前有调研过一些已有的产品,像twitter的beanstalkd,就是用来处理延时的任务的。不过当时由于在测试环境跑起来了,但是在线上却一直不可用,另外也没有人熟悉这个产品,怕后期带来运维的问题,所以也没有用起来,于是决定自己开发了。google一把,很多人在吹嘘用mongodb替换消息队列(MQ),看起来也很简单,于是我们准备试一下,用来做延时队列。这一试,就定下来了,然后在线上跑了1年多了。

先说一下我们部署mongodb的机器配置,4核CPU,8G内存的VM。你没看错,我们所有数据库都部署在淘宝聚石塔的虚拟机上面。这里不是做广告,如果你要做应用,建议不要用聚石塔,哈哈。我们是做淘宝业务,有“安全性”的限制,所以才用。

之所以用mongodb做“队列”,是因为mongodb有一个叫findAndModify的操作,这个操作是原子性的,也就是你可以修改一条记录的同时把老的记录返回。基于这个操作,我们可以在把一条记录标记为处理中的同时获取到这条数据,这样别人如果同时也是进行这样的处理,由于这个操作具备原子性,你们处理的任务不会重复,所以简单实现了出列的问题。所以简单的, 我们可以这样设计我们的延时队列里面的字段设计如下:

timestamp:出列的时间戳,这样我们可以根据系统当前的时间判断该记录要不要出列

status:状态,用来标记该记录目前是可以出列还是已经出列。0、1就可以搞定了

data:数据,表示你要保存到队列中的数据,出列后根据这个数据来处理任务。

定义好了,出列的操作就是:


//获取当前时间戳
long current = getCurrentTimeMillis();
//0是初始化状态,表示可以出列, 1表示已经出列过了.
DBObject obj = collection.findAndModify({status:0, timestamp:{$lte: current}},{$set:{status: 1}});
//没有命中, 说明当前不需要出列
if(obj == null){
return null;
}
//获取数据, 序列化为二进制的话, 可以兼容各种各样的数据格式
byte[] data = obj.get("data");
//反序列化
return SerializableUtil.deserial(data);

上面是伪代码, 既不是java的也不是javascript的.

是不是很简单,我们的队列搞定了。
如果故事到这里就结束了,那我就不写这篇文章了,呵呵呵呵呵。

由于业务的增长,我们在延时队列里的数据越来越多,处理任务的机器不够用了,不得不剥离成多台机来处理这些任务,这时候作为一个“队列”的挑战才刚刚开始。

由于多台机,你必须要让每台机处理的数据没有重复。上面的findAndModify已经很好的满足需求了是不是?是的,确实满足了。但是findAndModify有个致命的弱点,就是不支持批量findAndModify。由于每天要处理上千万的延时数据,如果全部请求都压到mongodb上,我们的mongodb吃不消,关键我们也不想多加机器。所以问题来了,我们要批量的findAndModify。于是乎,我们顺延这findAndModify的特性,自己弄了一套批量操作的findAndModify,代价是引入分布式锁,在批量find和批量modify的这段时间加分布式锁,这样能保证数据不会被重复处理。由于分布式锁我们用了zookeeper来实现,zookeeper的链路稳定性是我见过最差了,没有之一。时不时客户端就会断开连接,时不时客户端就会session expired。而面对这些问题,zookeeper是没有直接提供接口给你解决的,因为他的接口实在是太底层了,估计写那套client的人以前是搞底层系统开发的,所有问题都要调用方来处理,真不是一般的难用。zookeeper的问题始终是可以解决的,但接下来一个更大的问题是队列的吞吐量(qps)。由于改成了批量处理之后,mongodb平时的负载确实变低了好多。而平时一次出列1000条数据,不会带来拥堵的问题,直到那一天。

没错,那一天就是2013年的天猫双十一,我们系统之前为此做了扩容,机器数量从平时的20台加到了50+台。我们预估当天的数据量会达到平日的6倍。但实际上那天的量,远超我们想象。订单量达到了平时的10倍以上,在双十一前10分钟,我们的延时队列就被冲垮了。我着急了,一看,堵了100+W的数据了。过了一个小时之后,这个堵的数据量达到了800+W。眼看着这么多订单,白花花的就溜走了。直到第二天10点,我们终于解决了这个问题。

解决方案很简单粗暴,就是把批量findAndModify这套方案直接干掉,由于总共有6台任务处理机器,我们直接使用单机的模式,一台机器使用一个队列,把分布式锁抛弃掉。也是用批量的方式,批量出列,处理数据,然后批量删除。效率极高,在后面的几次高峰期都顶住了压力,“顺利”度过了双十一。这种模式也带来一个问题,如果处理数据的机器下线了怎么办?这涉及到了数据迁移的问题,好解决,我们写了个小工具,可以很快的把遗留的数据迁移到另外的机器上。

另外的解决方案是把架构搞成C/S的,做成一个单独的server来访问mongodb,任务处理系统都来订阅这个server。这样也不会有锁的问题,只不过当业务量继续增加,需要扩展服务器的时候,同样的问题还是会来临。上面的单机解决方案看起来是很简单,但实际上效果也是很不错的。当你真正贴近业务的时候,你会发现不是要做一个架构多么牛逼的系统,而是应该做一个能简单维护却能很好解决问题的系统。

如果你想设计延时队列,可以先参考java的延时队列的实现:

java.util.concurrent.DelayQueue

如果可以直接在上面加一层持久层,就可以简单实现延时队列了。

一些总结:能不用锁的地方一定不要用锁,锁会把你的性能耗尽。能不用分布式锁的地方一定不要用,如果你的系统需要使用分布式锁,可以想想有没有其他的简单粗暴的方案,也许会有更好的搜获。分布式系统的一个特点是高并发,如果架上了分布式锁,很可能让你的系统变成了串行处理的系统了,这样就违背分布式系统的初衷了。

附 beanstalkd 项目地址:http://kr.github.io/beanstalkd/

 

关于用户研究

昨天晚上,黄猿师兄(@小猪和蜜桃的故事)过来给我们分享了关于用户研究的一些心得,感觉很不错,所以也分享一下。

1. 用户研究是隶属于UED的,在产品的各个阶段都需要用到他。

在产品未出来的时候,用户研究需要先行,进行用户调研,市场调研,竞品分析等等。在产品出来之后需要进行收集用户反馈,可用性测试,用户数据分析来改善产品的用户体验,最后当积累了一定量的大数据的时候,可以通过挖掘用户数据来推断出接下来做什么产品会受用户欢迎。大数据不仅仅是属于后端同学使用的,实际上数据的分析在改善产品方面有很大的作用。

2. 用户体验的五大层面。

从上到下是表现成,框架层,结构层,功能层和战略层。最顶层是用户直接能感受到的东西,而最底层是用户感知不到的,看起来很虚,但却很影响深远的一层。产品的形成,往往是由战略层开始,通过市场调研,发现做某类产品有市场,于是交给下游的产品经理做功能的需求设计,产品经理最后把功能和结构层框架层跟交互设计师一起确定,把产品的交互稿定下来,接下来就是UI表现层了。所以一个产品的产生往往是由战略层来决定的,他一开始影响深远,但随着时间的推移,他的影响会越来越小,直到战略方向要大调整的时候, 整个产品也即将面临大改版了。如下图

用户体验5要素的影响和时间的关系
用户体验5要素的影响和时间的关系

也可以看出,如果一个产品一开始的战略定位不对,后面调整起来,就是全盘都要调整了。(大公司的表现是,当产品换了一个新的老大,往往一个新的老大会想表现自己的‘杰出才能’,调整产品的战略,导致之做的很多工作要重新调整,最终反馈到用户的是:“哇塞,你们又升级啦!”“尼玛!这个按钮怎么没了?”“卧槽,排序算法又变了?” 等等)

3. 用户研究的手段

分类起来可以归结为两种:一种是面向人的定性研究,另一种是面向数据的定量研究

在做面向人的定性研究的时候,必须要把人物角色和使用场景都考虑到:

人物角色:好比说一个产品的使用者,是一个什么样的人?一个淘宝卖家客服,还是白领消费者,还是公司CEO,这些都必须要归类出来。然后对这些人分别进行定性研究。“你不能拿着你的社交APP去问一个还不回走路的小孩他的感觉是什么样的。” 也就是说你要先定位你要分析的人群, 然后再给这些人群归类,通过提取共性,抽象出人物角色。再进行分析。

使用场景:这是一个非常重要的因素却往往容易被忽视。比如你办公司的宽带是20M的,你不能把你的用户请过来你的办公司,让他体验你的产品顺不顺畅,而是应该走到你的用户所在的环境中去对用户进行调研。还有一些土豪公司,在自己的办公楼搞了一个XX产品用户体验式,里面是豪华的设备装修,8G16核的机器,然后邀请一批用户到你这里,给他们冲上一杯香浓的咖啡,然后让他们来体验产品,这种做法是跟用户的真正使用场景完全脱节的,这时候用户给你的反馈,一点用处都没有。

另外必须要注意的是,用户真正的需求,往往不是他们说出来的,而是通过观察感受出来的。人往往会有虚伪的一面,你问他,直接的反馈不一定是真实的反馈。(比如你要做一个男性成人用品的产品,需要知道用户的JJ长度分布情况,然后你调查的结果肯定会比大多数人实际长度长一些,无法做到客观。)

定量分析:

根据数据来做定量分析的结果,一般是比较客观的(数据足够多的时候)。但是数据只是反馈了客观的数据,你不能加上主观的猜测然后刚好符合数据的表现就说明是这样的。比如,你一个功能做上去好久发现没人用,然后数据分析的结果证实了这一点,如果这时候你说这个功能没用是不客观的,你要看这个功能有没有入口,然后在用户的使用场景下,找到这个入口困不困难等等。数据是客观的,但不能为了‘佐证’你某个观点而滥用数据。

数据分析一般是用来验证你产品新功能的决策正不正确。比如你通过ABtest发现新功能的用户活跃度和消费度更高了,说明这是一个好的功能可以上。反之,应该继续改进产品。

数据挖掘不同于数据分析,他问问可以挖出用户潜在的需求,这是用于帮助产品新功能或者说新产品的决策的。比如美国著名的视频网站netflix就是通过大数据分析他们的用户平时都喜欢看什么类型的电视剧,最后发现用户喜欢看剧情片,所以出钱投资拍摄了美剧《纸牌屋》,现在已经第二季了。当然数据挖掘的难度比数据分析更大,更有创造性。

题外话:netflix我用过他们的开源zookeeper客户端curator, 确实把zookeeper那些shi一样的接口包装得容易用了许多。(不过还是有少许bug, session expired后有些时候没有自动重连)如果你要用到zookeeper,可以试试他们的框架。

响应式交互网站

最近把博客升级了一下:

1. 主题换成了响应式(responsive)的主题

2. 评论系统使用disqus

响应式web设计
响应式web设计

为什么要这样呢?

随着移动互联网的崛起,越来越多的网站把他们的站点迁移到移动端,小公司(业务比较单一)的做法是直接做一个APP把web端的内容用app重新实现一把,而大公司一般(由于业务太多)是做一个移动平台的APP的皮,内嵌一个webview控件,里面跑的是H5的代码,可以保证各大智能手机平台(IOS, android)上访问到的内容是一模一样的,同时保证了开发的迭代速度。数据还是来自原来的数据,只不过是输出HTML5,同时做了响应式的交互罢了。这样同时可以保证APP不过于庞大的同时能提供跟web上一样的服务。在以前,wap流行的时候,wap浏览器渲染能力太弱,页面真心难看,所以大公司也不会太关注wap端的发展。但是智能操作系统的告诉发展为h5的流行带来了契机,移动端的体验容易上去了,各大小公司也就都来搞一把了。

响应式交互最早我care是谁提出来的,但是我最早知道的一个响应式交互这个词是来自于titter的bootstrap 的css框架,那时候(2011)还不懂什么是响应式交互,只不过感叹于这个框架的强大,所以用了起来。虽然他不兼容IE6,IE7两个原始浏览器,但是用来做后台系统的管理界面,那就甩出什么extjs,jquery-ui(我们都用过)好几条大街。如果你正好需要快速搭建后台系统,建议使用bootstrap。实际上现在好多创业型的小公司,为了快速开发同事保持界面的整洁,也采用了bootstrap框架来做开发(如果你团队缺乏专业的前端人才,正好拿来使用),兼容的事情,总会有别的框架帮你搞定。(我们的做法是只兼容到IE7,IE6直接提示用户去升级浏览器,否则是无法使用的。)

后来越来越多的地方有人提到了响应式交互,我的理解是,实际上就是自适应式的交互,页面的内容能够根据你浏览器当前的window size来调整内容(要么隐藏,要么换行)的显示,以期望你的网站在各种分辨率下都能正常访问。目前国内大多数网站的大多数页面都还是固定宽度的(900,950,1000等),很少有整体网站做了响应式交互的,因为实在不好实现。像国内UED团队比较活跃的淘宝网,也仅仅是首页和“我的淘宝”做了响应式的设计,除了这两个之外的其他页面都是固定宽度的(1000)同时主内容居中显示。

到了移动端,我们发现这个做响应式页面的难度降低了,因为目前主流的移动平台IOS和Android上的浏览器几乎都是全面支持H5和CSS3的,这样的话再移动端使用CSS3来写响应式的页面难度就大大降低了,这是移动互联网天然的优势,也是他之于pc互联网有大进步的地方。当然目前H5和CSS3在android平台上表现出来的性能还是不佳,导致一些使用app内容webview来加载h5内容的app我们经常要等好久(比如手机淘宝的app,用三星的s4,渲染一个页面还是太久,比iPhone5要慢个1两秒),另外是canvas的性能也不咋地。当然这主要是android系统性能不够优化的原因,但是我们看到android的进步很快,相信很快这两个系统直接的性能差距就会没有了,到时候正是H5和CSS3统一移动端的时候。

所以,你如果打算做一个新时代的网站,注意要多考虑响应式(responsive)交互以及移动互联网。

扩展阅读:

响应式网页设计  http://zh.wikipedia.org/zh-cn/%E5%93%8D%E5%BA%94%E5%BC%8F%E7%BD%91%E9%A1%B5%E8%AE%BE%E8%AE%A1

响应式web设计 http://www.yixieshi.com/ucd/11828.html

java 线程的几种状态

java thread的运行周期中, 有几种状态, 在 java.lang.Thread.State 中有详细定义和说明:

NEW 状态是指线程刚创建, 尚未启动

RUNNABLE 状态是线程正在正常运行中, 当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等, 这个状态下发生的等待一般是其他系统资源, 而不是锁, Sleep等

BLOCKED  这个状态下, 是在多个线程有同步操作的场景, 比如正在等待另一个线程的synchronized 块的执行释放, 或者可重入的 synchronized块里别人调用wait() 方法, 也就是这里是线程在等待进入临界区

WAITING  这个状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 一遍该线程可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在理解点里面wait等待别人notify, 线程调用了join方法 join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束

TIMED_WAITING  这个状态就是有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态

TERMINATED 这个状态下表示 该线程的run方法已经执行完毕了, 基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收)

下面谈谈如何让线程进入以上几种状态:

1. NEW, 这个最简单了,  
 
     static void NEW() {
          Thread t = new Thread ();
         System. out.println(t.getState());
    }
 
输出NEW
 
2. RUNNABLE, 也简单, 让一个thread start, 同时代码里面不要sleep或者wait等
 
   private static void RUNNABLE() {
         Thread t = new Thread(){
             
              public void run(){
                  for(int i=0; i<Integer.MAX_VALUE; i++){
                      System. out.println(i);
                 }
             }
             
         };
         
         t.start();
    }
 
 71e94764e28c7f8bbd3ef91c1c0088b4
 
3. BLOCKED, 这个就必须至少两个线程以上, 然后互相等待synchronized 块
          
     private static void BLOCKED() {
         
          final Object lock = new Object();
         
         Runnable run = new Runnable() {
             
              @Override
              public void run() {
                  for(int i=0; i<Integer.MAX_VALUE; i++){
                      
                       synchronized (lock) {
                          System. out.println(i);
                      }
                      
                 }
             }
         };
         
         Thread t1 = new Thread(run);
         t1.setName( “t1”);
         Thread t2 = new Thread(run);
         t2.setName( “t2”);
         
         t1.start();
         t2.start();
         
    }
 
8e9ad1eadf9d38c0b6c8cb024cb36c0c
这时候, 一个在RUNNABLE, 另一个就会在BLOCKED (等待另一个线程的 System.out.println.. 这是个IO操作, 属于系统资源, 不会造成WAITING等)
 
4. WAITING, 这个需要用到生产者消费者模型, 当生产者生产过慢的时候, 消费者就会等待生产者的下一次notify
 
     private static void WAITING() {
 
          final Object lock = new Object();
         Thread t1 = new Thread(){
              @Override
              public void run() {
                 
                  int i = 0;
                 
                  while(true ){
                       synchronized (lock) {
                           try {
                               lock.wait();
                          } catch (InterruptedException e) {
                          }
                          System. out.println(i++);
                      }
                 }
             }
         };
         
         Thread t2 = new Thread(){
              @Override
              public void run() {
                 
                  while(true ){
                       synchronized (lock) {
                           for(int i = 0; i< 10000000; i++){
                              System. out.println(i);
                          }
                          lock.notifyAll();
                      }
                      
                 }
             }
         };
         
         t1.setName( “^^t1^^”);
         t2.setName( “^^t2^^”);
         
         t1.start();
         t2.start();
    }
 
 b43a3d9b67bab266ffea4537fb043bba
 
5. TIMED_WAITING, 这个仅需要在4的基础上, 在wait方法加上一个时间参数进行限制就OK了.
 
把4中的synchronized 块改成如下就可以了.
 
synchronized (lock) {
   try {
      lock.wait(60 * 1000L);
   } catch (InterruptedException e) {
   }
   System. out .println(i++);
 }
 
 88d9047d8a709c2d63c695bcf58a0297
另外看stack的输出,  他叫 TIMED_WAITING(on  object monitor) , 说明括号后面还有其他的情况, 比如sleep, 我们直接把t2的for循环改成sleep试试:
 
synchronized (lock) {
    
    try {
          sleep(30*1000L);
    } catch (InterruptedException e) {
    }
    lock.notifyAll();
}
a37ef4c72c00e793f8b6c746d74fd4d9 
 
看到了吧, t2的state是 TIMED_WAITING( sleeping),  而t1依然是on object monitor , 因为t1还是wait在等待t2 notify, 而t2是自己sleep
 
另外, join操作也是进入 on object monitor
 
6. TERMINATED, 这个状态只要线程结束了run方法, 就会进入了…
 
    private static void TERMINATED() {
         Thread t1 = new Thread();
         t1.start();
         System. out.println(t1.getState());
          try {
             Thread. sleep(1000L);
         } catch (InterruptedException e) {
         }
         System. out.println(t1.getState());
    }
输出: 
RUNNABLE
TERMINATED
 
由于线程的start方法是异步启动的, 所以在其执行后立即获取状态有可能才刚进入RUN方法且还未执行完毕
 
 
废话了这么多, 了解线程的状态究竟有什么用?
所以说这是个钓鱼贴么…
 
好吧, 一句话, 在找到系统中的潜在性能瓶颈有作用.
 
当java系统运行慢的时候, 我们想到的应该先找到性能的瓶颈, 而jstack等工具, 通过jvm当前的stack可以看到当前整个vm所有线程的状态, 当我们看到一个线程状态经常处于
WAITING 或者 BLOCKED的时候, 要小心了, 他可能在等待资源经常没有得到释放(当然, 线程池的调度用的也是各种队列各种锁, 要区分一下, 比如下图)
6db341bbd7680bbc2e6ae37a66329397
这是个经典的并发包里面的线程池, 其调度队列用的是LinkedBlockingQueue, 执行take的时候会block住, 等待下一个任务进入队列中, 然后进入执行, 这种理论上不是系统的性能瓶颈, 找瓶颈一般先找自己的代码stack,再去排查那些开源的组件/JDK的问题
 
排查问题的几个思路:
 
0. 如何跟踪一个线程?
看到上面的stack输出没有, 第一行是内容是 threadName priority tid nid desc
更过跟踪tid, nid 都可以唯一找到该线程.
 
1. 发现有线程进入BLOCK, 而且持续好久, 这说明性能瓶颈存在于synchronized块中, 因为他一直block住, 进不去, 说明另一个线程一直没有处理好, 也就这个synchronized块中处理速度比较慢, 然后再深入查看. 当然也有可能同时block的线程太多, 排队太久造成.
 
2. 发现有线程进入WAITING, 而且持续好久, 说明性能瓶颈存在于触发notify的那段逻辑. 当然还有就是同时WAITING的线程过多, 老是等不到释放.
 
3. 线程进入TIME_WAITING 状态且持续好久的, 跟2的排查方式一样.
 
 
上面的黑底白字截图都是通过jstack打印出来的, 可以直接定位到你想知道的线程的执行栈, 这对java性能瓶颈的分析是有极大作用的.
 
NOTE: 上面所有代码都是为了跟踪线程的状态而写的, 千万不要在线上应用中这么写…

struts2 获取文件上传进度方法

一、上传机制

1. struts2 的 Dispacher 类中 wrapRequest 方法, 将http请求头中带有multipart/form-data 开头的请求都会包装成为 MultiPartRequest 的实例

 Dispacher.wrapRequest

这里 MultiPartRequestWrapper 构造方法就是用MultiPartRequest 再包装一层, 同时还会调用他的parse 方法, 进行文件的上传

2. 这里getContainer().getInstance 得到的对象, 是从struts的 ObjectFactory中得到的(可以在对应的struts配置文件的<bean>标签找到), 查看struts默认配置文件 struts-default.xml 可以看到下面这一句

<bean type=“org.apache.struts2.dispatcher.multipart.MultiPartRequest” 
name=“jakarta” class=“org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest” scope=“default” optional=“true” />

这里的定义, 可以理解为:  Interface obj = cls.newInstance() 写法变成xml而已

default里面定义了两个, name 分别是 struts 和 jakarata, 作用却是完全一样的, 支持文件的分块上传, 这样大文件的上传才不会出问题.

3. 进入JakartaMultiPartRequest 类, 查看 parse方法代码

可以看出, 这里用的是 commons-fileupload 包来做文件上传的.

4. commons-fileupload 包中, ServletFileUpload 类可以使用一个 progressListener 回调对象来监听上传进度, 而这个JakartaMultiPartRequest 类中已经写死了, 没有使用任何listener:

 ServletFileUpload upload = new ServletFileUpload(fac);
 upload.setSizeMax(maxSize);
 List items = upload.parseRequest(createRequestContext(servletRequest));

这时候因为没有注入到监听器, 而且在这里会parseRequest, 也就是把文件上传过来并保存在saveDir目录中, 最后走到action,交给业务去处理。

以上是整个上传的机制,struts2通过封装commons-fileupload,直接免去了文件上传代码的编写,需要做的仅仅是在action里面保存文件以及一些后续工作而已。

但是上面的第4点中提到,因为代码写死了,没有指定监听器,也不能通过注入的形式(因为ServletFileUpload对象是临时new出来的),所以导致文件上传的过程中我们无法知道进度。

二、解决方案

5. 先理解为什么struts2为什么会使用jakarta来包装文件上传的请求,其实是在

     struts.multipart.parser 这个常量中指定的,默认情况下

   <constant name=“struts.multipart.parser” value=“jakarta”></constant>

   这里的value,就是在struts-default.xml 中定义的bean的name  (见第2节)

6. 所以解决的方法是, 我们自己来实现MultiPartRequest  这个接口, 然后自己定义一个bean, 比如叫 jakartaExt, 然后把5中的parser常量的值设置为jakartaExt, 就可以替换系统默认的包装, 用我们自己定义的了, 这个时候我们就可以给ServletFileUpload对象注入listener了.

注入listener
配置

注: 这里的uploadId仅是为了区分不同的上传线程而已(同一个用户可以开多个页面同时上传)

尝试上传文件, 可以看到日志打出来的结果:

2013-03-06 11:48:15,DEBUG,file upload, url=http://localhost/struts2/upload.htm
2013-03-06 11:48:15,DEBUG,[1362541661052] upload progress:0%
2013-03-06 11:48:28,DEBUG,[1362541661052] upload progress:10%
2013-03-06 11:48:38,DEBUG,[1362541661052] upload progress:20%
2013-03-06 11:48:48,DEBUG,[1362541661052] upload progress:30%
2013-03-06 11:48:58,DEBUG,[1362541661052] upload progress:40%
2013-03-06 11:49:08,DEBUG,[1362541661052] upload progress:50%
2013-03-06 11:49:19,DEBUG,[1362541661052] upload progress:60%
2013-03-06 11:49:30,DEBUG,[1362541661052] upload progress:70%
2013-03-06 11:49:40,DEBUG,[1362541661052] upload progress:80%
2013-03-06 11:49:52,DEBUG,[1362541661052] upload progress:90%
2013-03-06 11:50:05,DEBUG,[1362541661052] upload progress:100%
2013-03-06 11:50:05,INFO,[1362541661052] contentLength:2869568 parse use 109772ms

这样, 我们就可以通过ajax的形式, 从session中获取到对应uploadId的上传进度, 页面也就可以做出进度条了.

7. 另一种解决方案

     就是采用servlet来上传, 自己也用commons-fileupload组件来操作, 要注意的是, 把web.xml中struts2的filter-mapping 不能是 /*, 而是应该对应的 /*.action /*.htm 之类的, 否则如果拦截了所有请求, 这时候会先经过struts2的 filter而导致request被wrap掉(wrap的过程就会有parse的操作),导致servlet有响应的时候, 实际上已经上传好了…

我个人还是倾向6的做法, 因为这时候技能做到上传与业务无关, 也能监控进度, 如果在servlet中做的话, 你每上传一种文件都要一段新的处理逻辑挤进去(因为每种文件的处理逻辑不一样)

Java 慎用方法级别的synchronized关键字

为什么要这么说呢, 因为笔者被这个坑过(其实是自己坑自己)╮(╯_╰)╭

先看一段synchronized 的详解:

synchronized 是 java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。

四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

五、以上规则对其它对象锁同样适用.
简单来说, synchronized就是为当前的线程声明一个锁, 拥有这个锁的线程可以执行区块里面的指令, 其他的线程只能等待获取锁, 然后才能相同的操作.
这个很好用, 但是笔者遇到另一种比较奇葩的情况.
1. 在同一类中, 有两个方法是用了synchronized关键字声明
2. 在执行完其中一个方法的时候, 需要等待另一个方法(异步线程回调)也执行完, 所以用了一个countDownLatch来做等待
3. 代码解构如下:
synchronized void  a(){
  countDownLatch = new CountDownLatch(1);
  // do someing
  countDownLatch.await();
}

synchronized void b(){
     countDownLatch.countDown();
}
其中
a方法由主线程执行, b方法由异步线程执行后回调
执行结果是:
主线程执行 a方法后开始卡住, 不再往下做, 任你等多久都没用.
这是一个很经典的死锁问题
a等待b执行, 其实不要看b是回调的, b也在等待a执行. 为什么呢? synchronized 起了作用.
一般来说, 我们要synchronized一段代码块的时候, 我们需要使用一个共享变量来锁住, 比如:
byte[]  mutex = new byte[0];

void a1(){
     synchronized(mutex){
          //dosomething
     }
}

void b1(){

     synchronized(mutex){
          // dosomething
     }

}
如果把a方法和b方法的内容分别迁移到 a1和b1 方法的synchronized块里面, 就很好理解了.
a1执行完后会间接等待(countDownLatch)b1方法执行
然而由于 a1 中的mutex并没有释放, 就开始等待b1了, 这时候, 即使是异步的回调b1方法, 由于需要等待mutex释放锁, 所以b方法并不会执行
于是就引起了死锁
而这里的synchronized关键字放在方法前面, 起的作用就是一样的. 只是java语言帮你隐去了mutex的声明和使用而已. 同一个对象中的synchronized 方法用到的mutex是相同的, 所以即使是异步回调, 也会引起死锁, 所以要注意这个问题. 这种级别的错误是属于synchronized关键字使用不当. 不要乱用, 而且要用对.
那么这样的 隐形的mutex 对象究竟是 什么呢?
很容易想到的就是 实例本身. 因为这样就不用去定义新的对象了做锁了. 为了证明这个设想, 可以写一段程序来证明.
思路很简单, 定义一个类, 有两个方法, 一个方法声明为 synchronized, 一个在 方法体里面使用synchronized(this), 然后启动两个线程, 来分别调用这两个方法, 如果两个方法之间发生锁竞争(等待)的话, 就可以说明 方法声明的 synchronized 中的隐形的mutex其实就是 实例本身了.
public class MultiThreadSync {

    public synchronized void m1() throws InterruptedException{
         System. out.println("m1 call" );
         Thread. sleep(2000);
         System. out.println("m1 call done" );
    }

    public void m2() throws InterruptedException{
          synchronized (this ) {
             System. out.println("m2 call" );
             Thread. sleep(2000);
             System. out.println("m2 call done" );
         }
    }

    public static void main(String[] args) {
          final MultiThreadSync thisObj  = new MultiThreadSync();

         Thread t1 = new Thread(){
              @Override
              public void run() {
                  try {
                      thisObj.m1();
                 } catch (InterruptedException e) {
                      e.printStackTrace();
                 }
             }
         };

         Thread t2 = new Thread(){
              @Override
              public void run() {
                  try {
                      thisObj.m2();
                 } catch (InterruptedException e) {
                      e.printStackTrace();
                 }
             }
         };

         t1.start();
         t2.start();

    }

}
结果输出是:
m1 call
m1 call done
m2 call
m2 call done
说明方法m2的sync块等待了m1的执行. 这样就可以证实 上面的设想了.
另外需要说明的是, 当sync加在 static的方法上的时候, 由于是类级别的方法, 所以锁住的对象是当前类的class实例. 同样也可以写程序进行证明.这里略.
所以方法的synchronized 关键字, 在阅读的时候可以自动替换为synchronized(this){}就很好理解了.
                                        void method(){
void synchronized method(){                 synchronized(this){
      // biz code                               // biz code
}                             ------>>>      }
                                        }

Bloomfilter 原理与应用

前言: 本文只讲解原理, 不讲解BloomFilter中各项指标的公式的推算, 能让你知道有这么一个东西(what), 他能达到什么效果(why), 是如何做到的(how).
(What)

Bloom Filter 是用一个 位数组(数组的每个元素不是1就是0) 来表示一个大的元素集合, 而且通过这个数组就可以判断某个元素是不是属于这个集合

大大节省了空间, 但是有代价的, 就是有一定的错误率(为什么会有, 后面举个例说明一下)
假定我们有一个已知的集合,  S = {a1, a2, … , an} (这里的元素不仅仅是数字, 而是泛指所有对象), 我们要判断一个元素 x 是否属于这个集合, 普通的做法有两种形式:
     1. 遍历 比较,  性能和S的大小成反比     (时间问题)
     2. hash+链表(传说中的hashMap), 速度较快, 但是要把集合的所有数据都存进内存   (空间问题)
在性能要求高, 而且空间不足的情况下, BloomFilter就派上用场了
(Why)
BloomFilter能解决什么问题?
     以少量的内存空间判断一个 元素 是否属于这个集合, 代价是有一定的错误率
(How)
工作原理
     1. 初始化一个数组, 所有位标为0,  A={x1, x2, x3,…,xm}  (x1, x2, x3,…,xm 初始为0)
     2. 将已知集合S中的每一个数组, 按以下方式映射到A中
          2.0  选取n个互相独立的hash函数 h1, h2, … hk
          2.1  将元素通过以上hash函数得到一组索引值 h1(xi), h2(xi),…,hk(xi)
          2.2  将集合A中的上述索引值标记为1(如果不同元素有重复, 则重复覆盖为1, 这是一个觅等操作)
     3.  对于一个元素x, 将其根据2.0中选取的hash函数, 进行hash, 得到一组索引值 h1(x), h2(x), …,hk(x)
          如果集合A中的这些索引位置上的值都是1, 表示这个元素属于集合S, 否则则不属于S
几个前提
     1. hash函数的计算不能性能太差, 否则得不偿失
     2. 任意两个hash函数之间必须是独立的.
          即任意两个hash函数不存在单一相关性, 否则hash到其中一个索引上的元素也必定会hash到另一个相关的索引上, 这样多个hash没有意义
错误率
     工作原理的第3步, 的出来的结论, 一个是绝对靠谱的, 一个是不能100%靠谱的.
     如果集合A中的这些索引位置上的值都是1, 表示这个元素属于集合S, 否则则不属于S     标红的这句话是绝对靠谱的.
     至于错误率有多大, 我这里不想去推算, 后面会给出参考文章, 不在本文的讨论范围, 很简单就能举个例子说明错误率是存在的
         当A长度不是很大时, 很容易出现一种情况, 使得A上的元素全部被标记为1了, 这时所有的元素都会被认为是S里的元素, 所以, 错误率是存在的!
          (可以看出, 错误的大小跟A的长度以及hash函数的个数有关)
(In Action)
应用
1. 假如你有一个很大的商品库(亿级别), 然后你要做一个浏览型的网站, 这时候, 你不可能把所有的商品都丢给用户去浏览, 而是从商品中挑选出部分属于
     “精品”的商品来给用户浏览, 提高用户体验和转化率, 你对你的精品库建立一套搜索引擎
2. 由于互动需要, 你需要对你维护的精品库的商品数据实时更新动态, 比如XXX在某时间给了一个”好评”,  “购买了一笔”, “赞”,”喜欢”等等
3. 为了实现这种实时更新, 你通过MQ订阅了商品相关的消息(notify)(交易, 评价, SNS), 只要商品发生动态就会发送给你的系统.
4. 这时候, 由于全网的商品很多, 发生动态的消息很多情况下是跟你的精品库没有关系的, 这时候你需要挡掉这些消息, 不进行处理.
5. 此时, 不可能每来一个商品数据你先通过搜索引擎判断一下商品不是在你的精品库内(效率问题, 压力问题), 这时候, bloomFilter派送用场了.
6. 从上面错误率的点, 我们可以看到, 如果一个元素被BloomFilter判断为不属于原有的集合, 那么这个元素是肯定不属于这个集合的(被排除的准确率是100%的)
     通过bloomfilter的几项指标, 就可以挡掉大多数没有相关的数据, 而只处理有关系(虽然有部分无关)的数据了.
(Graphics)附图一张
参考文章:
     http://blog.csdn.net/jiaomeng/article/details/1495500
     http://en.wikipedia.org/wiki/Bloom_filter

mongodb 修改器学习

1. 执行update相关操作都可以使用修改器.
2. 修改器是为了不整个大文档来替换修改, 而是修改局部
3. 详情:
3.1 $inc (自增)
为增加某个字段的数值
例子:
原文档如下:

{
 "_id" : ObjectId("4b253b067525f35f94b60a31"),
 "url" : "www.example.com",
 "pageviews" : 52
}

执行修改器 $inc
db.test.update({url:’www.example.com’},{$inc:{pageviews: 1}})
表示为pageviews这个 key 的值 加上1. 后面是对应要加上的值.
注意, $inc中, 字段对应的值只能是 数字. 但是可以是 正数也可以是负数. 如果是负数, 就是执行一个减法操作.
3.2 $set (设置)

修改某个字段的值为指定的值. 这里的修改是与字段类型无关的. 你可以把一个string修改成long或者一个对象都行.
例子:
原文档如下:

{
"_id" : ObjectId("4b253b067525f35f94b60a31"),
"name" : "joe",
"age" : 30,
"sex" : "male",
"location" : "Wisconsin"
}

修改性别, 变成false (String -> Boolean)
执行修改器 $set

db.test.update({name:'joe'}, {$set:{sex:false}})

表示将 name为joe的这个对象的 sex修改为false
3.3 $unset (删除字段)
这个修改器是为了删除某个指定的字段及其值.
例子:
原文档如下:

{
 "_id" : ObjectId("4f9105377522000000006be0"), 
 "howlong" : 6, 
 "lovePerson" : "zhanying", 
 "person" : "jiacheo"
}

把howlong删除
执行修改器 $unset

db.test.update({person:'jiacheo'},{$unset:{howlong:1}})

后面的1没具体意义, 相当于确认
3.4 $push (把一个数据放到数组里面)
例子:
原文档如下:

{
 "_id" : ObjectId("4f9105377522000000006be0"), 
 "lovePerson" : "zhanying", 
 "person" : "jiacheo", 
 "supporters" : [ "gongjin" ]
}

执行修改器, 增加一个人到supporters里面

db.test.update({person:'jiacheo'},{$push:{supporters:'ziming'}})

这时候变成:

{ 
 "_id" : ObjectId("4f9105377522000000006be0"),
 "lovePerson" : "zhanying",
 "person" : "jiacheo",
 "supporters" : [
   "gongjin", "ziming" 
 ]
}

若果再执行一遍上面的代码, 那么数组还是会加上同样的名称, 如果你需要排同, 可以用后面这个修改器
3.5 $addToSet (将一个对象放到集合里面. 集合里面不会出现两个重复的一模一样的对象)
例子:
原文档如下:

{
"_id" : ObjectId("4f9105377522000000006be0"),
"lovePerson" : "zhanying",
"person" : "jiacheo",
"supporters" : [
"gongjin",
"ziming"
]
}

执行修改器 $addToSet

db.test.update({person:'jiacheo'},{$addToSet:{supporters:'ziming'}})

文档内容没有改变..
以上两个修改器还可以配合 $each, 把一个数据里的数据append到另一个数组上去(后者会排重)
比如:

db.test.update({person:'jiacheo'}, {$addToSet:{supporters:{$each: ["gongjin","ziming","liuxun","feidu"] }}})

执行后, 结果如下:

{
 "_id" : ObjectId("4f9105377522000000006be0"),
 "lovePerson" : "zhanying",
 "person" : "jiacheo",
 "supporters" : ["gongjin", "ziming", "feidu", "liuxun" 
 ]
}

3.6 $pop 队列出列 (按数组索引顺序出列(正序或者倒序))
例子:
原文档如下:

{
 "_id" : ObjectId("4f9105377522000000006be0"), 
 "lovePerson" : "zhanying", 
 "person" : "jiacheo",
 "supporters" : ["gongjin", "ziming", "feidu", "liuxun" 
 ]
}

执行修改器 $pop

db.test.update({person:'jiacheo'},{$pop:{supporters:1}})

修改后变为

{
 "_id" : ObjectId("4f9105377522000000006be0"),
 "lovePerson" : "zhanying",
 "person" : "jiacheo",
 "supporters" : [ "gongjin", "ziming", "feidu" ]
}

value 值为1 表示数组索引靠后的先出列, 也就是后进先出, 相当于出栈的概念. value值为-1表示数组索引考前的先出列, 也就是先进先出, 相当于一个FIFO的队列
3.7 $pull (符合条件的元素出列)
与上一个相比, 这个修改器会把符合条件的元素从数组中删除, 而不是简单的按照索引值的大小来处理.
原文档如下:

{
 "_id" : ObjectId("4f9105377522000000006be0"),
 "lovePerson" : "zhanying",
 "person" : "jiacheo",
 "supporters" : [ "gongjin", "ziming", "feidu" ]
}

执行修改器 $pull

db.test.update({person:'jiacheo'},{$pull:{supporters: 'gongjin'}})

修改后:

{
 "_id" : ObjectId("4f9105377522000000006be0"),
 "lovePerson" : "zhanying",
 "person" : "jiacheo",
 "supporters" : [ "ziming", "feidu" ]
}

可以见, gongjin从supporters中被remove掉了.
这里所有被匹配的元素都会被出列

tomcat thread dump 分析

  1. 前言

Java Thread Dump 是一个非常有用的应用诊断工具, 通过thread dump出来的信息, 可以定位到你需要了解的线程, 以及这个线程的调用栈. 如果配合linux的top命令, 可以找到你的系统中的最耗CPU的线程代码段, 这样才能有针对性地进行优化.

  1. 场景和实践

    2.1. 后台系统一直是在黑盒运行, 除了能暂停一部分任务的执行, 根本无法知道哪些任务耗CPU过多。所以一直以为是业务代码的问题, 经过各种优化(删减没必要的逻辑, 合并写操作)等等优化, 系统负载还是很高. 没什么访问量, 后台任务处理也就是每天几百万的级别, load还是达到了15以上. CPU只有4核,天天收到load告警却无从下手, 于是乎就被迫来分析一把线程.

   2.2 系统跑的是java tomcat, 要触发tomcat thread dump很简单, 先找到tomcat对应的进程id, 我们设置为PID
   【linux 命令】:  ps -ef | grep tomcat
   可以找到, 然后给这个进程发送一个QUIT的信号量, 让其触发线程的dump,  下面的操作先别急着动手, 等到看完2.3再动手不迟
    【linux 命令】: kill -3 $PID   /  kill -QUIT $PID
tomcat会把thread dump的内容输出到控制台
     【linux 命令】:cd $tomcathome/logs/
查看 catalina.out 文件, 把最后的跟thread相关的内容获取出来.
大致内容如下:
2012-04-13 16:30:41
Full thread dump OpenJDK 64-Bit Server VM (1.6.0-b09 mixed mode):
"TP-Processor12" daemon prio=10 tid=0x00000000045acc00 nid=0x7f19 in Object.wait() [0x00000000483d0000..0x00000000483d0a90]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00002aaab5bfce70> (a org.apache.tomcat.util.threads.ThreadPool$ControlRunnable)
at java.lang.Object.wait(Object.java:502)
at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:662)
- locked <0x00002aaab5bfce70> (a org.apache.tomcat.util.threads.ThreadPool$ControlRunnable)
at java.lang.Thread.run(Thread.java:636)

"TP-Processor11" daemon prio=10 tid=0x00000000048e3c00 nid=0x7f18 in Object.wait() [0x00000000482cf000..0x00000000482cfd10]
java.lang.Thread.State: WAITING (on object monitor)
....
"VM Thread" prio=10 tid=0x00000000042ff400 nid=0x77de runnable"GC task thread#0 (ParallelGC)" prio=10 tid=0x000000000429c400 nid=0x77d9 runnable

"GC task thread#1 (ParallelGC)" prio=10 tid=0x000000000429d800 nid=0x77da runnable

"GC task thread#2 (ParallelGC)" prio=10 tid=0x000000000429ec00 nid=0x77db runnable

"GC task thread#3 (ParallelGC)" prio=10 tid=0x00000000042a0000 nid=0x77dc runnable

"VM Periodic Task Thread" prio=10 tid=0x0000000004348400 nid=0x77e5 waiting on condition

JNI global references: 815

Heap
PSYoungGen      total 320192K, used 178216K [0x00002aaadce00000, 0x00002aaaf1800000, 0x00002aaaf1800000)
eden space 303744K, 55% used [0x00002aaadce00000,0x00002aaae718e048,0x00002aaaef6a0000)
from space 16448K, 65% used [0x00002aaaf0690000,0x00002aaaf110c1b0,0x00002aaaf16a0000)
to   space 16320K, 0% used [0x00002aaaef6a0000,0x00002aaaef6a0000,0x00002aaaf0690000)
PSOldGen        total 460992K, used 425946K [0x00002aaab3a00000, 0x00002aaacfc30000, 0x00002aaadce00000)
object space 460992K, 92% used [0x00002aaab3a00000,0x00002aaacd9f6a30,0x00002aaacfc30000)
PSPermGen       total 56192K, used 55353K [0x00002aaaae600000, 0x00002aaab1ce0000, 0x00002aaab3a00000)
object space 56192K, 98% used [0x00002aaaae600000,0x00002aaab1c0e520,0x00002aaab1ce0000)
最后一段是系统的对内存的使用情况.
2.3. 要知道thread dump是不会告诉你每个线程的负载情况的, 需要知道每个线程的负载情况, 还得靠top命令来查看.
    【linux 命令】:top -H -p $PID
这时候, 可以看到java进程下各个线程的负载和内存等使用情况. 也不用全部搞下来, 只要top几个负载过高的记录即可(最好按下SHIFT+T 按CPU耗时总时间倒序排序,这样找到的top几个是最耗CPU时间的,而且系统启动时间应该持续15分钟以上,这样容易看出哪个线程耗时多。)
     大致内容如下:
Tasks: 118 total,   2 running, 116 sleeping,   0 stopped,   0 zombie
Cpu(s): 92.6%us,  2.3%sy,  0.0%ni,  3.8%id,  0.7%wa,  0.1%hi,  0.7%si,  0.0%st
Mem:   4054168k total,  3892212k used,   161956k free,   115816k buffers
Swap:  4192956k total,   294448k used,  3898508k free,  2156024k cachedPID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
8091 admin     16   0 1522m 814m 9660 R 22.3 20.6   4:05.61 java
8038 admin     16   0 1522m 814m 9660 R 10.3 20.6   2:46.31 java
8043 admin     15   0 1522m 814m 9660 S  3.7 20.6   1:52.04 java
8039 admin     15   0 1522m 814m 9660 S  0.7 20.6   2:10.98 java
8041 admin     15   0 1522m 814m 9660 S  0.7 20.6   1:39.66 java
8009 admin     15   0 1522m 814m 9660 S  0.3 20.6   0:27.05 java
8040 admin     15   0 1522m 814m 9660 S  0.3 20.6   0:51.46 java
7978 admin     25   0 1522m 814m 9660 S  0.0 20.6   0:00.00 java
7980 admin     19   0 1522m 814m 9660 S  0.0 20.6   0:05.05 java
7981 admin     16   0 1522m 814m 9660 S  0.0 20.6   0:06.31 java
7982 admin     15   0 1522m 814m 9660 S  0.0 20.6   0:06.50 java
7983 admin     15   0 1522m 814m 9660 S  0.0 20.6   0:06.66 java
7984 admin     15   0 1522m 814m 9660 S  0.0 20.6   0:06.87 java
7985 admin     15   0 1522m 814m 9660 S  0.0 20.6   0:33.82 java
几个字段跟top的字段意思是一致的, 就是这里的 PID是 线程在系统里面的ID, 也就是进程每创建一个线程, 不仅进程自己会分配ID, 系统也会的. 接下来的问题排查就是主要根据这个PID来走的.
看到上面的部分数据, 当前正在跑的任务中, CPU占用最高的几个线程ID
2.4. 如果不借助工具, 自己分析的话, 可以把PID字段从10进制数改为 16进制, 然后到threaddump日志中去查找一把, 找对对应的线程上下文信息, 就可以知道哪段代码耗CPU最多了.
比如 8091  的16进制是 1F9B, 查找 thread dump 日志中, nid=0x1F9B 的线程( 这里的nid意思是nativeid, 也就是上面讲的系统为线程分配的ID), 然后找到相关的代码段, 进行优化即可.
比如
"链路检测" prio=10 tid=0x00002aaafa498000 nid=0x1F9B runnable [0x0000000045fac000..0x0000000045facd10]</div>

java.lang.Thread.State: RUNNABLE
at cn.emay.sdk.communication.socket.AsynSocket$CheckConnection.run(AsynSocket.java:112)
at java.lang.Thread.run(Thread.java:636)
可以看出, 这是一个 发短信的客户端的链路检测引擎的系统负载飙升. (实际上这个线程引起的负载绝不止这么一点.)
2.5 第三方的jar包, 我感到顿时泪奔. 接下来是反编译, 看详细的代码… 果然是有一段死循环监听的… 目前是像他们要一份SDK的源代码, 或者要他们进行优化。
2.6 使用工具的话, 可以看到更多一点的信息, java的tda工具就是专门分析thread dump的.
具体功能自己去挖掘啦.