Java thread 线程的6种状态及转换

Java 线程共有6种状态:

  1. NEW 一个线程刚创建, 尚未开始运行. 这种状态不会出现在 thread dump 种. 比如代码中 new 了一个 Thread(), 还没调用 start() 方法, 就是 NEW 这个状态.
  2. RUNNABLE 线程正在运行或可运行状态,但是被挂起. 线程运行都是基于时间片的, 只要线程属于可运行状态(RUNNABLE), 就会给它分配时间片, 一旦时间片分配到该线程, 它就立马能运行.
  3. BLOCKED 线程正在等待一个 monitor 锁. 它处于该锁的 block 队列里面. 这个完全是跟 java 的 synchronize 关键字有关, 在线程等待进入 synchronize 里面的同步区之前, 尚未获得锁的时候, 就处于 BLOCKED 状态. 也只有这种情况, 线程才能进入 BLOCKED 状态;
  4. WAITING 线程正在等待其它线程触发某种操作, 在其它线程没有触发某种操作之前, 它只能在这个 WAITING 状态等待;
  5. TIMED_WAITING 同上面的 WAITING 状态, 只不过它有最长等待时间, 一旦时间到, 它就退出这种 TIMED_WAITING 状态;
  6. TERMINATED 线程以及结束. 这种状态也不可能出现在 thread dump 当中.

    其中 NEW 和 TERMINATED 都很容易理解, 一个是开始之前, 一个是结束之后, 都不会出现在 thread dump 中. RUNNABLE 表示线程处在正在运行或随时可运行状态, 只要给它分配到时间片, 它立马就能运行. 所以 RUNNABLE 状态并不代表它正在运行.

    只有当一个线程想要进入 Synchronize 保护的代码区时候, 才有可能进入 BLOCKED 状态. 如果它获得了 monitor 锁, 那么它直接进入被保护的代码块, 还是 RUNNABLE 状态. 如果没获得,那么进入该 monitor 的 block 队列(Entry Set), 线程则变成 BLOCKED 状态. 另外一个很容易忽略的地方是,当一个线程曾经拥有 monitor 锁, 然后因为某种原因需要挂起(已经进入被 synchronize 保护的代码区, 由于某种条件不满足, 不能退出被保护的代码区, 只能挂起当前线程), 这时该线程调用 Object.wait() 方法, 线程变成 WAITING 状态, 这时线程进入该 monitor 的 wait 队列 (Wait Set), 如果有其它线程调用该对象的 notify/notifyAll 方法, 那么这些线程被从 wait 队列 移入 block 队列( Entry Set), 这时该线程又要重新竞争 monitor 锁, 这时线程状态变化为 BLOCKED 状态. 所以不论是直接竞争锁未得到锁, 还是因为之前由于 wait 进入 Wait Set, 后来被 notify 之后, 重新进入 Entry Set 等待竞争monitor 锁, 只有这2种情况, 线程会进入 BLOCKED 状态.

    对于 WAITING 状态, 只有下面这几种情况, 线程才可能进入 WAITING 状态:

    1. Object.wait with no timeout
    2. Thread.join with no timeout
    3. LockSupport.park

      通过 Object.wait 进入的线程, 进入该 monitor 锁的 Wait Set, 等待该 Object 的 notify/notifyAll 事件;

      通过 Thread.join 进入的线程, 等待另外一个线程的结束. 实际的实现还是通过 synchronize 的 monitor 锁和 wait 方法实现的. 假如有线程 threadA 和 threadB 两个线程, threadA 通过调用 threadB.join() 方法等待 threadB 的结束. 实际实现是 threadB 对象的 join 方法是 synchronized, 它先是获得了 threadB 对象的 monitor 锁, 然后又进入了该 monitor 的 wait() 方法, 进入 Wait Set, 进入等待状态. 当 threadB 结束运行之前, 它会调用 threadB 对象的 notifyAll() 方法, 那么所有在 Wait Set 的线程进入 Entry Set, 之后竞争 threadB 对象的 monitor 锁, 然后释放锁, 继续执行, 最终通过这种方式实现对其它线程的终止状态的等待.

      通过 LockSupport.park 进入的线程, 一般都是通过 AQS 进入的, 它们都在等待 LockSupport.unpark() 方法.

      对于 TIMED_WAITING 状态, 下面这几种情况会进入该状态:

    4. Thread.sleep
    5. Object.wait with timeout
    6. Thread.join with timeout
    7. LockSupport.parkNanos
    8. LockSupport.parkUntil

tomcat 7 servlet annotation StackOverflowError

今天部署代码的时候, 遇到如下的出错:

SEVERE: Error waiting for multi-thread deployment of WAR files to complete
java.util.concurrent.ExecutionException: java.lang.StackOverflowError
    at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:252)
    at java.util.concurrent.FutureTask.get(FutureTask.java:111)
    at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:751)
    at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:471)
    at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1412)
    at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:312)
    at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:119)
    at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90)
    at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:401)
    at org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:346)
    at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:1145)
    at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:782)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1566)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1556)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
    at java.util.concurrent.FutureTask.run(FutureTask.java:166)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)
Caused by: java.lang.StackOverflowError
    at java.util.Random.nextInt(Random.java:239)
    at sun.misc.Hashing.randomHashSeed(Hashing.java:254)
    at java.util.HashMap.<init>(HashMap.java:255)
    at java.util.HashMap.<init>(HashMap.java:305)
    at java.util.HashSet.<init>(HashSet.java:103)
    at org.apache.catalina.startup.ContextConfig.populateSCIsForCacheEntry(ContextConfig.java:2169)
    at org.apache.catalina.startup.ContextConfig.populateSCIsForCacheEntry(ContextConfig.java:2188)
    at org.apache.catalina.startup.ContextConfig.populateSCIsForCacheEntry(ContextConfig.java:2188)
    at org.apache.catalina.startup.ContextConfig.populateSCIsForCacheEntry(ContextConfig.java:2188)
    at org.apache.catalina.startup.ContextConfig.populateSCIsForCacheEntry(ContextConfig.java:2188)
    at org.apache.catalina.startup.ContextConfig.populateSCIsForCacheEntry(ContextConfig.java:2188)
    at org.apache.catalina.startup.ContextConfig.populateSCIsForCacheEntry(ContextConfig.java:2188)
    at org.apache.catalina.startup.ContextConfig.populateSCIsForCacheEntry(ContextConfig.java:2188)
    at org.apache.catalina.startup.ContextConfig.populateSCIsForCacheEntry(ContextConfig.java:2188)
    at org.apache.catalina.startup.ContextConfig.populateSCIsForCacheEntry(ContextConfig.java:2188)
    ......

上面出错stack递归, 导致溢出. 这段代码应该发生在Servlet 容器启动的时候自动扫描 servlet 的 annotation时.
google 一下, 发现上述的情况在下面这个bug 中有描述:
https://issues.apache.org/bugzilla/show_bug.cgi?id=53871, RESOLVED FIXED Version: 7.0.35
上面出错的代码所用的tomcat 版本为: Version 7.0.27

那么换做 tomcat 7.0.40实验一下, 报出如下的出错内容 (出错的error message 被优化, 明确指出出错的原因):

Caused by: java.lang.IllegalStateException: Unable to complete the scan for annotations for web application [] due to a StackOverflowError. Possible root causes
 include a too low setting for -Xss and illegal cyclic inheritance dependencies. The class hierarchy being processed was 
 [org.jaxen.util.AncestorOrSelfAxisIterator->org.jaxen.util.AncestorAxisIterator->org.jaxen.util.AncestorOrSelfAxisIterator]
        at org.apache.catalina.startup.ContextConfig.checkHandlesTypes(ContextConfig.java:2179)

问题出在2个Class的依赖关系, 在一个包中, A 是B的之类, 另外一个包中, B 却是A的之类:
jaxen-1.1.6.jar 包中的依赖关系

public class org.jaxen.util.AncestorOrSelfAxisIterator implements java.util.Iterator
public class org.jaxen.util.AncestorAxisIterator extends org.jaxen.util.AncestorOrSelfAxisIterator

jaxen-core-1.0-FCS.jar 包中的依赖关系:

public class org.jaxen.util.AncestorOrSelfAxisIterator extends org.jaxen.util.AncestorAxisIterator
public class org.jaxen.util.AncestorAxisIterator extends org.jaxen.util.StackedIterator

解决方案: 在pom.xml 中找到2个jar, 把其中一个 exclude 掉, 去试试, 是不是好了

有人也遇到同样的问题:
http://hongjiang.info/2014/06/

windows 系统无法 粘贴 复制 copy/paste not working

最近在 chrome, eclipse, outlook 中有时候没法粘贴, 复制, 不论是 Ctrl+C, Ctrl+V 还是先选中在右键复制/粘贴, 都不管用.
在最后忍无可忍的时候, 突然想起来, 以前发生过, 只要关掉 windows 远程桌面就好了.

这应该是windows 远程桌面的 rdpclip.exe 的一个 Bug, 一开始还没问题, 如果远程桌面开的时间长了, 就会出现这个问题.

小米手机充电器 拆解

不知道从那天开始, 小米手机的充电器无法充电了, 仔细检查一下, 应该是充电器接触不良, 刚插入的时候, 能冲一瞬间, 有时候, 用手使劲掰住, 也能冲一会, 一旦放开, 就又无法充电了. 于是就想打开充电器看看到底怎么回事.

首先百度搜到官方论坛的这么一个帖子: 小米充电器辛苦拆卸全程, 真是惨不忍睹, 竟然锯开, 不过还是从回帖中发现, 需要从插头那头拆开. 开始动手了, 首先说下工具:
1) 美工刀, 用来划开粘住的接缝;
2) 钳子, 用力夹住插头, 拔出里面元器件;
3) 电笔, 测试里面某些东西上有没有电;
4) 胶水, 最后把他们在粘到一块.

直接上图吧:
全套工具, 注意看充电器插头那端的接缝, 那是用美工刀的尖一点点划的结果.
IMG\_20150124\_173947.jpg
当用美工刀把四边都弄出缝来之后, 可以用钳子夹住插头, 活动几下, 然后拔出:
IMG\_20150124\_173521.jpg
IMG\_20150124\_173512.jpg
最后使用不带壳的充电器, 小心有电...
D8007DB4FEFE1C6D83B6E422B72CBD27.png

我这个拆开再装上, 竟然就能充电了, 装的时候, 用胶水把划开的地方全部粘一下.