昨天接到市场的一个bug
在生成xml文件的时候整个Eclipse RCP卡死了
提到xml就不得不想到著名的“GFW导致DTD访问无能而Dom4j做DTD校验如果发现有网络能通会一直等到超时所以超级慢”的bug。 当然,最后排查发现和这个bug无关,这个bug解决方案: 1、保持断网状态 2、保持FQ状态 3、本地化DTD 4、使用国内镜像DTD
使用jconsole对线程进行检查,发现主线程卡在了System.out.println
原因是锁被另外一个线程持有,如果连System.out.println都要卡住,这还怎么写代码?
经过一番排查,发现了问题所在。
首先来看看System.out.println(String)的源码:
public void println(String arg0) { synchronized (this) { this.print(arg0); this.newLine(); } }
可以注意到,同步语句块的对象监视器是该PrintStream自身。
在Eclipse RCP环境中,System.out进行了转发,执行具体操作的是IOConsoleOutputStream对象。
最终,会落到IOConsolePartitioner.streamAppended来处理,关键源码如下:
public void streamAppended(IOConsoleOutputStream stream, String s) throws IOException { ...synchronized(pendingPartitions) { ...if (fBuffer > 160000) { if(Display.getCurrent() == null){ try { pendingPartitions.wait(); } catch (InterruptedException e) { } } else { processQueue(); } } } }
Display.getCurrent==null为true时,当前线程为非UI线程,反之则是UI线程。
fBuffer则是同一个线程不间断执行steamAppended时录入的字符串长度。
processQueue则类似于flush,用于把buffer展示到console上,然后归零计数器,并且执行pendingPartitions.notifyAll()。
这一串儿代码的意思是说:
1、如果某个线程不间断录入了超过16万个字符,则判断它是不是UI线程
2、如果是UI线程,则直接输出,并且唤醒其他阻塞的非UI线程
3、如果不是UI线程,则阻塞
看上去似乎挺好的,但是结合起来我们会发现几个问题:
1、如果一个非UI线程执行了System.out.println(String),比如输出长篇小说,抱歉,会卡死,除非过程中有个UI线程帮助输出;
2、如果一个非UI线程已经录入了16万个字符导致自己wait,所有调用steamAppended的外层同步锁都会死锁。
这是因为,wait仅仅只是释放了当前锁,也就是之前源码中对象监视器pendingPartitions监视的同步语句块
而System.out.println是由PrintStream监视的,锁依然被该非UI线程持有,就算UI线程打算通过out.println来触发processQueue的notifyAll,也不可能办到了。
在当前,我遇到的问题是eclipse的常见svn插件subclipse在执行members来遍历svn resource时,会检查resource.isIgnore,这个过程如果出现了版本不匹配一类的错误,会直接输出log,这个log,由于members是个无阻塞的树遍历,有可能超出16万字符串。
我目前的解决方式是换subversion插件,当然,可以尝试在UI线程中处理svn log。
但是,eclipse RCP的这个bug至少,至今还是存在的。