分类 Java 相关 下的文章

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 状态, 下面这几种情况会进入该状态:
1. Thread.sleep
2. Object.wait with timeout
3. Thread.join with timeout
4. LockSupport.parkNanos
5. 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/

java util WeakHashMap

  1. WeakHashMap 只是key 是WeakReference 的, 当key 被GC 回收后, value 如果没有其他Reference, 很快也会被回收;
  2. 很多时候, 一定要避免 value 直接或间接的Reference key;
  3. WeakHashMap 的key 和value 都可以是 null;
  4. 如果事前知道Map的大小, 可以设置 initial capacity and load factor 来提高性能;
  5. WeakHashMap 默认不是 synchronized, 若要需要线程安全的WeakHashMap, 需要通过 Collections.synchronizedMap 构建;