Java线程中线程block和wait状态态和block状态的区别

博客分类:
这两天为了定位JBOSS老是挂死的问题,学习了一下JAVA多线程方面的知识,在此总结一下
1、在Java程序中,JVM负责线程的调度。线程调度是指按照特定的机制为多个线程分配CPU的使用权。
调度的模式有两种:分时调度和抢占式调度。分时调度是所有线程轮流获得CPU使用权,并平均分配每个线程占用CPU的时间;抢占式调度是根据线程的优先级别来获取CPU的使用权。JVM的线程调度模式采用了抢占式模式。
2、Thread类实际上也是实现了Runnable接口的类。
在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。
3、JAVA多线程涉及到2个问题,一个是线程的调度,另一个是线程的同步
4、线程的状态有:new、runnable、running、waiting、timed_waiting、blocked、dead
当执行new Thread(Runnable r)后,新创建出来的线程处于new状态,这种线程不可能执行
当执行thread.start()后,线程处于runnable状态,这种情况下只要得到CPU,就可以开始执行了。runnable状态的线程,会接受JVM的调度,进入running状态,但是具体何时会进入这个状态,是随机不可知的
running状态中的线程最为复杂,可能会进入runnable、waiting、timed_waiting、blocked、dead状态:
如果CPU调度给了别的线程,或者执行了Thread.yield()方法,则进入runnable状态,但是也有可能立刻又进入running状态
如果执行了Thread.sleep(long),或者thread.join(long),或者在锁对象上调用object.wait(long)方法,则会进入timed_waiting状态
如果执行了thread.join(),或者在锁对象上调用了object.wait()方法,则会进入waiting状态
如果进入了同步方法或者同步代码块,没有获取锁对象的话,则会进入blocked状态
处于waiting状态中的线程,如果是因为thread.join()方法进入等待的话,在目标thread执行完毕之后,会回到runnable状态;如果是因为object.wait()方法进入等待的话,在锁对象执行object.notify()或者object.notifyAll()之后会回到runnable状态
处于timed_waiting状态中的线程,和waiting状态中的差不多,只不过是设定时间到了,就会回到runnable状态
处于blocked状态中的线程,只有获取了锁之后,才会脱离阻塞状态
当线程执行完毕,或者抛出了未捕获的异常之后,会进入dead状态,该线程结束
5、当线程池中线程都具有相同的优先级,调度程序的JVM实现自由选择它喜欢的线程。这时候调度程序的操作有两种可能:一是选择一个线程运行,直到它阻塞或者运行完成为止。二是时间分片,为池内的每个线程提供均等的运行机会。
6、设置线程的优先级:线程默认的优先级是创建它的执行线程的优先级。可以更改线程的优先级。
JVM从不会改变一个线程的优先级。然而,1-10之间的值是没有保证的。一些JVM可能不能识别10个不同的值,而将这些优先级进行每两个或多个合并,变成少于10个的优先级,则两个或多个优先级的线程可能被映射为一个优先级。
7、Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
8、另一个问题是线程的同步,这个我感觉比调度更加复杂一些
Java中每个对象都有一个“内置锁”,也有一个内置的“线程表”
当程序运行到非静态的synchronized方法上时,会获得与正在执行代码类的当前实例(this实例)有关的锁;当运行到同步代码块时,获得与声明的对象有关的锁
释放锁是指持锁线程退出了synchronized方法或代码块。
当程序运行到synchronized同步方法或代码块时对象锁才起作用。
一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。
9、当提到同步(锁定)时,应该清楚是在哪个对象上同步(锁定)?
obj.wait()
obj.notify()
obj.notifyAll()
关于这3个方法,有一个关键问题是:
必须从同步环境内调用wait()、notify()、notifyAll()方法。只有拥有该对象的锁的线程,才能调用该对象上的wait()、notify()、notifyAll()方法
与每个对象具有锁一样,每个对象也可以有一个线程列表,他们等待来自该对象的通知。线程通过执行对象上的wait()方法获得这个等待列表。从那时候起,它不再执行任何其他指令,直到调用对象的notify()方法为止。如果多个线程在同一个对象上等待,则将只选择一个线程(不保证以何种顺序)继续执行。如果没有线程等待,则不采取任何特殊操作。
11、下面贴几个代码实例,配合jstack命令说明一下
public class ThreadA {
public static void main(String[] args) {
ThreadB b = new ThreadB();// ThreadB status: new
b.start();// ThreadB status: runnable
synchronized (b) {
System.out.println("等待对象b完成计算。。。");
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("b对象计算的总和是:" + b.total);
public class ThreadB extends Thread {
public void run() {
synchronized (this) {
for (int i = 0; i & 101; i++) {
notifyAll();
jstack输出的结果是:
"main" prio=6 tid=0x nid=0x1638 waiting on condition [0x]
&& java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at net.kyfxbl.lock.ThreadA.main(ThreadA.java:20)
- locked &0x22a18a90& (a net.kyfxbl.lock.ThreadB)
"Thread-0" prio=6 tid=0x02bbb800 nid=0x1410 waiting for monitor entry [0x02f0f000]
&& java.lang.Thread.State: BLOCKED (on object monitor)
at net.kyfxbl.lock.ThreadB.run(ThreadB.java:11)
- waiting to lock &0x22a18a90& (a net.kyfxbl.lock.ThreadB)
可以看到,主线程和新线程在同一个对象上锁定,主线程的方法里执行了Thread.sleep(60000),因此进入了TIMED_WAITING状态,而新线程则进入BLOCKED状态
public class ThreadA {
public static void main(String[] args) {
ThreadB b = new ThreadB();// ThreadB status: new
b.start();// ThreadB status: runnable
synchronized (b) {
System.out.println("等待对象b完成计算。。。");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("b对象计算的总和是:" + b.total);
public class ThreadB extends Thread {
public void run() {
synchronized (this) {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
for (int i = 0; i & 101; i++) {
notifyAll();
jstack输出的结果是:
"main" prio=6 tid=0x nid=0x1684 in Object.wait() [0x]
&& java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on &0x22a18b08& (a net.kyfxbl.lock.ThreadB)
at java.lang.Object.wait(Object.java:485)
at net.kyfxbl.lock.ThreadA.main(ThreadA.java:22)
- locked &0x22a18b08& (a net.kyfxbl.lock.ThreadB)
"Thread-0" prio=6 tid=0x02bcc800 nid=0x19c waiting on condition [0x02f0f000]
&& java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at net.kyfxbl.lock.ThreadB.run(ThreadB.java:12)
- locked &0x22a18b08& (a net.kyfxbl.lock.ThreadB)
2个线程还是在同一个对象上同步,但这次主线程立刻执行了b.wait()方法,因此释放了对象b上的锁,自己进入了WAITING状态。接下来新线程得到了对象b上的锁,所以没有进入阻塞状态,紧接着执行Thread.sleep(60000)方法,进入了TIMED_WAITING状态
public class ThreadA {
public static void main(String[] args) {
ThreadB b = new ThreadB();// ThreadB status: new
b.start();// ThreadB status: runnable
synchronized (b) {
System.out.println("等待对象b完成计算。。。");
b.wait();// ThreadB status: running
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("b对象计算的总和是:" + b.total);
public class ThreadB extends Thread {
public void run() {
synchronized (this) {
for (int i = 0; i & 101; i++) {
notifyAll();
System.out.println("我要睡了");
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
jstack输出的结果是:
"main" prio=6 tid=0x nid=0x3ec in Object.wait() [0x]
&& java.lang.Thread.State: BLOCKED (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on &0x22a18ba0& (a net.kyfxbl.lock.ThreadB)
at java.lang.Object.wait(Object.java:485)
at net.kyfxbl.lock.ThreadA.main(ThreadA.java:20)
- locked &0x22a18ba0& (a net.kyfxbl.lock.ThreadB)
"Thread-0" prio=6 tid=0x02bbb800 nid=0x14b4 waiting on condition [0x02f0f000]
&& java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at net.kyfxbl.lock.ThreadB.run(ThreadB.java:19)
- locked &0x22a18ba0& (a net.kyfxbl.lock.ThreadB)
当主线程执行b.wait()之后,就进入了WAITING状态,但是新线程执行notifyAll()之后,有一个瞬间主线程回到了RUNNABLE状态,但是好景不长,由于这个时候新线程还没有释放锁,所以主线程立刻进入了BLOCKED状态
12、当在对象上调用wait()方法时,执行该代码的线程立即放弃它在对象上的锁。然而调用notify()时,并不意味着这时线程会放弃其锁。如果线程仍然在完成同步代码,则线程在移出之前不会放弃锁。因此,只要调用notify()并不意味着这时该锁被释放
13、与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的并非没机会执行。
14、在一个线程中开启另外一个新线程,则新开线程称为该线程的子线程,子线程初始优先级与父线程相同。
15、JRE判断程序是否执行结束的标准是所有的前台执线程行完毕了,而不管后台线程的状态,因此,在使用后台线程时候一定要注意这个问题。
16、下面说说我们这次JBOSS挂死问题的处理方法
现象:系统运行一段时间之后,发现有几个子系统无法访问了,但是另外几个可以。CPU占用达到100%
观察了一下,发现无法访问的应用都部署在同一个JBOSS里,于是把该JBOSS的堆栈用jstack命令输出
发现里面有大量的线程处于BLOCKED状态,均是在执行到c3p0的一个方法里的某一行时,BLOCKED住了
于是下载c3p0的源码,跟进去看了一下,这是一个同步方法,内部会去获取数据库连接,如果获取到连接,就进行下一步操作,如果获取不到,就执行sleep(long timeout)方法。
反推一下,我猜测可能是这样的:
由于某段代码没有释放数据库连接--&连接池中的连接耗尽--&部分线程无限TIMED_WAITING--&其余线程都BLOCKED--&开启新线程--&频繁引发GC--&占用大量CPU--&应用挂起
后来对所有涉及到数据库连接的代码进行排查,发现确实有几个地方做完数据库操作以后,没有释放连接。把这部分代码改掉,重新启动JBOSS,没有再出现JBOSS挂起的现象
浏览 12473
论坛回复 /
(15 / 23199)
浏览: 1137196 次
来自: 深圳
http://archive.apache.org/dist/ ...
好文章,解决了大问题!
不错,值得借鉴
赞!!!!!!
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'谈谈Java线程状态
本来以为这个知识点自己已经很了解了,但最近跟同事讨论Java线程对应的状态以及转换过程的时候,发现还是有一些没理清楚的地方,或者说争议点,趁机梳理了一下这块的知识,自己也动手做了实验,写篇文章总结一下。
先看一下Thread类中关于状态的源码:
public enum State {
* Thread state for a thread which has not yet started.
* Thread state for a runnable thread.
A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
&li&{@link Object#wait() Object.wait} with no timeout&/li&
&li&{@link #join() Thread.join} with no timeout&/li&
&li&{@link LockSupport#park() LockSupport.park}&/li&
* &p&A thread in the waiting state is waiting for another thread to
* perform a particular action.
* For example, a thread that has called &tt&Object.wait()&/tt&
* on an object is waiting for another thread to call
* &tt&Object.notify()&/tt& or &tt&Object.notifyAll()&/tt& on
* that object. A thread that has called &tt&Thread.join()&/tt&
* is waiting for a specified thread to terminate.
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
&li&{@link #sleep Thread.sleep}&/li&
&li&{@link Object#wait(long) Object.wait} with timeout&/li&
&li&{@link #join(long) Thread.join} with timeout&/li&
&li&{@link LockSupport#parkNanos LockSupport.parkNanos}&/li&
&li&{@link LockSupport#parkUntil LockSupport.parkUntil}&/li&
TIMED_WAITING,
* Thread state for a terminated thread.
* The thread has completed execution.
TERMINATED;
这里特意贴了下英文注释,下面我挨个来解释一下: NEW状态: 英文翻译过来是线程还是没有开始执行。 首先,既然已经有状态了,那肯定是已经创建好线程对象了(如果对象都没有,何来状态这一说?),这样一来问题的焦点就在于还没有开始执行,我们都知道当调用线程的start()方法时,线程不一定会马上执行,因为Java线程是映射到操作系统的线程进行执行,此时可能还需要等操作系统调度,但此时该线程的状态已经为RUNNABLE了。
RUNNABLE状态: 英文翻译过来是该状态指运行中的线程,一个线程处于RUNNABLE状态的意思是在JVM层面它是在执行的,但是该线程可能是在等待操作系统的资源,比如说CPU。这个状态是最有争议的,注释中说了,它表示线程在JVM层面是执行的,但在操作系统层面不一定,它举例是CPU,毫无疑问CPU是一个操作系统资源,但这也就意味着在等操作系统其他资源的时候,线程也会是这个状态,这里就有一个关键点IO阻塞算是等操作系统的资源么? 我自己写代码测试了一下:
public class BIOServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8888);
Socket accept = serverSocket.accept();
System.out.println("连接");
accept.getInputStream().read();
System.out.println("读取");
代码很简单,开始的accept()会阻塞直到有连接进来,然后输入流的read()方法又会阻塞等数据进来,这个时候线程的状态应该是BLOKED?RUNNABLE? 我用jstack导出线程查了一下:
"Monitor Ctrl-Break" #10 daemon prio=5 os_prio=31 tid=0x09000 nid=0x5503 runnable [0x9000]
java.lang.Thread.State: RUNNABLE
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java)
at java.net.ServerSocket.implAccept(ServerSocket.java:545)
at java.net.ServerSocket.accept(ServerSocket.java:513)
at com.intellij.rt.execution.application.AppMain$1.run(AppMain.java:90)
at java.lang.Thread.run(Redefined)
java.lang.Thread.State: RUNNABLE!!! 从调用栈来看它确实是阻塞在accept()方法上,但是线程状态是RUNNABLE,是不是很意外?同理,read()方法也阻塞了,也处于RUNNABLE状态。 在回过头去理解JDK的英文注释,在等操作系统资源,比如说CPU,我们都知道Java的线程是映射到操作系统的线程上去执行的,也就是说操作系统的线程到底有没有在执行JVM是不知道的,JVM判断线程是否执行的逻辑就是自己的线程是否已经映射到了一个操作系统的线程,而不关系操作系统层面上该映射到的线程到底有没有再执行。 同理,NIO中调用select()方法时,线程状态也是RUNNABLE,这里就不贴代码了。
BLOCKED状态: 英文翻译过来是该状态表示线程在阻塞等待monitor lock(监视器锁)。一个线程在进入synchronized修饰的临界区的时候,或者在synchronized临界区中调用Object.wait然后被唤醒重新进入synchronized临界区都对应该状态。结合上面RUNNABLE的分析,也就是IO阻塞不会进入BLOCKED状态,只有synchronized会导致线程进入该状态。 关于BLOCKED状态,注释里只提到一种情况就是进入synchronized声明的临界区时会导致,这个也很好理解,synchronized是JVM自己控制的,所以这个阻塞事件它自己能够知道(对比理解上面的操作系统层面)。
WAITING状态: 代表线程进入等待状态。 当一个线程调用如下方法时会进入该状态: 1.Object. 2.Thread. 3.LockSupport.park 处在这个状态的线程是在等另一个线程做一些特殊的操作。比如Object.wait()方法在等另一个线程调用Object.notify()或者Object.notifyAll(),Thread.join()方法在等一个指定线程完成,即变为TERMINATED状态,当然,LockSupport.park则是在等另一个线程调用LockSupport.unpark方法。
TIMED_WAITING状态: 代表一个线程在等一个指定时间,然后自动醒来。 调用以下方法时,线程会进入该状态: 1.Thread.sleep 2.Object.wait(long) 3.Thread.join(long) 4.LockSupport.parkNanos 5.LockSupport.parkUntil
TERMINATED状态: 当线程执行完成时会进入该状态。
通过上面的分析,我画了一个Java线程状态转换图:
Java线程状态转换图
写在最后: Java线程状态应该算是一个有争议的问题,因为它涉及到操作系统本身。上面的内容只是我自己的理解跟做实验的结论,如果大家有不一样的看法或者其他实验证明我上面的观点有问题,欢迎给我评论或者私信,期待跟大家交流。
文章转自:本文地址:
你可能对这些文章感兴趣.
Copyright & 2017 &&java面试题博客分类:
一:jstack
jstack命令的语法格式: jstack
&pid&。可以用jps查看java进程id。这里要注意的是:1. 不同的 JAVA虚机的线程 DUMP的创建方法和文件格式是不一样的,不同的 JVM版本, dump信息也有差别。本文中,只以 SUN的 hotspot JVM 5.0_06 为例。
2. 在实际运行中,往往一次 dump的信息,还不足以确认问题。建议产生三次 dump信息,如果每次 dump都指向同一个问题,我们才确定问题的典型性。
二:线程分析
2.1. JVM 线程
在线程中,有一些 JVM内部的后台线程,来执行譬如垃圾回收,或者低内存的检测等等任务,这些线程往往在 JVM初始化的时候就存在,如下所示:
"Low Memory Detector" daemon prio=10 tid=0x nid=0x7 runnable [0xx]
"CompilerThread0" daemon prio=10 tid=0x08143c58 nid=0x6 waiting on condition [0xxfb5fd798]
"Signal Dispatcher" daemon prio=10 tid=0x08142f08 nid=0x5 waiting on condition [0xx]
"Finalizer" daemon prio=10 tid=0x08137ca0 nid=0x4 in Object.wait() [0xfbeed000..0xfbeeddb8]
at java.lang.Object.wait(Native Method)
- waiting on &0xef600848& (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:116)
- locked &0xef600848& (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:132)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)
"Reference Handler" daemon prio=10 tid=0x nid=0x3 in Object.wait() [0xfbf4a000..0xfbf4aa38]
at java.lang.Object.wait(Native Method)
- waiting on &0xef600758& (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:474)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116)
- locked &0xef600758& (a java.lang.ref.Reference$Lock)
"VM Thread" prio=10 tid=0x nid=0x2 runnable
"VM Periodic Task Thread" prio=10 tid=0x nid=0x8 waiting on condition
我们更多的是要观察用户级别的线程,如下所示:
"Thread-1" prio=10 tid=0x nid=0xa waiting on condition [0xef47a000..0xef47ac38]
at java.lang.Thread.sleep(Native Method)
at testthread.MySleepingThread.method2(MySleepingThread.java:53)
- locked &0xef63d600& (a testthread.MySleepingThread)
at testthread.MySleepingThread.run(MySleepingThread.java:35)
at java.lang.Thread.run(Thread.java:595)
我们能看到:
* 线程的状态: waiting on condition
* 线程的调用栈
* 线程的当前锁住的资源: &0xef63d600&
2.2. 线程的状态分析
正如我们刚看到的那样,线程的状态是一个重要的指标,它会显示在线程 Stacktrace的头一行结尾的地方。那么线程常见的有哪些状态呢?线程在什么样的情况下会进入这种状态呢?我们能从中发现什么线索?& /span&1.1 Runnable
该状态表示线程具备所有运行条件,在运行队列中准备操作系统的调度,或者正在运行。1.2 Wait on condition
该状态出现在线程等待某个条件的发生。具体是什么原因,可以结合 stacktrace来分析。最常见的情况是线程在等待网络的读写,比如当网络数据没有准备好读时,线程处于这种等待状态,而一旦有数据准备好读之后,线程会重新激活,读取并处理数据。在 Java引入 NewIO之前,对于每个网络连接,都有一个对应的线程来处理网络的读写操作,即使没有可读写的数据,线程仍然阻塞在读写操作上,这样有可能造成资源浪费,而且给操作系统的线程调度也带来压力。在 NewIO里采用了新的机制,编写的服务器程序的性能和可扩展性都得到提高。
如果发现有大量的线程都在处在 Wait on condition,从线程 stack看, 正等待网络读写,这可能是一个网络瓶颈的征兆。因为网络阻塞导致线程无法执行。一种情况是网络非常忙,几 乎消耗了所有的带宽,仍然有大量数据等待网络读 写;另一种情况也可能是网络空闲,但由于路由等问题,导致包无法正常的到达。所以要结合系统的一些性能观察工具来综合分析,比如 netstat统计单位时间的发送包的数目,如果很明显超过了所在网络带宽的限制 ; 观察 cpu的利用率,如果系统态的 CPU时间,相对于用户态的 CPU时间比例较高;如果程序运行在 Solaris 10平台上,可以用 dtrace工具看系统调用的情况,如果观察到 read/write的系统调用的次数或者运行时间遥遥领先;这些都指向由于网络带宽所限导致的网络瓶颈。另外一种出现 Wait on condition的常见情况是该线程在 sleep,等待 sleep的时间到了时候,将被唤醒。1.3 Waiting for monitor entry 和 in Object.wait()
在多线程的 JAVA程序中,实现线程之间的同步,就要说说 Monitor。 Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor。每个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 “Active Thread”,而其它线程都是 “Waiting Thread”,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的线程状态是 “Waiting for monitor entry”,而在 “Wait Set”中等待的线程状态是 “in Object.wait()”。
先看 “Entry Set”里面的线程。我们称被 synchronized保护起来的代码段为临界区。当一个线程申请进入临界区时,它就进入了 “Entry Set”队列。对应的 code就像:
synchronized(obj) {
这时有两种可能性:
该 monitor不被其它线程拥有, Entry Set里面也没有其它等待线程。本线程即成为相应类或者对象的 Monitor的 Owner,执行临界区的代码
该 monitor被其它线程拥有,本线程在 Entry Set队列中等待。
在第一种情况下,线程将处于 “Runnable”的状态,而第二种情况下,线程 DUMP会显示处于 “waiting for monitor entry”。如下所示:
"Thread-0" prio=10 tid=0x08222eb0 nid=0x9 waiting for monitor entry [0xf927b000..0xf927bdb8]
at testthread.WaitThread.run(WaitThread.java:39)
- waiting to lock &0xef63bf08& (a java.lang.Object)
- locked &0xef63beb8& (a java.util.ArrayList)
at java.lang.Thread.run(Thread.java:595)
临界区的设置,是为了保证其内部的代码执行的原子性和完整性。但是因为临界区在任何时间只允许线程串行通过,这 和我们多线程的程序的初衷是相反的。 如果在多线程的程序中,大量使用 synchronized,或者不适当的使用了它,会造成大量线程在临界区的入口等待,造成系统的性能大幅下降。如果在线程 DUMP中发现了这个情况,应该审查源码,改进程序。
现在我们再来看现在线程为什么会进入 “Wait Set”。当线程获得了 Monitor,进入了临界区之后,如果发现线程继续运行的条件没有满足,它则调用对象(一般就是被 synchronized 的对象)的 wait() 方法,放弃了 Monitor,进入 “Wait Set”队列。只有当别的线程在该对象上调用了 notify() 或者 notifyAll() , “ Wait Set”队列中线程才得到机会去竞争,但是只有一个线程获得对象的 Monitor,恢复到运行态。在 “Wait Set”中的线程, DUMP中表现为: in Object.wait(),类似于:
"Thread-1" prio=10 tid=0x nid=0xa in Object.wait() [0xef47a000..0xef47aa38]
at java.lang.Object.wait(Native Method)
- waiting on &0xef63beb8& (a java.util.ArrayList)
at java.lang.Object.wait(Object.java:474)
at testthread.MyWaitThread.run(MyWaitThread.java:40)
- locked &0xef63beb8& (a java.util.ArrayList)
at java.lang.Thread.run(Thread.java:595)
仔细观察上面的 DUMP信息,你会发现它有以下两行:
- locked &0xef63beb8& (a java.util.ArrayList)
- waiting on &0xef63beb8& (a java.util.ArrayList)
这里需要解释一下,为什么先 lock了这个对象,然后又 waiting on同一个对象呢?让我们看看这个线程对应的代码:
synchronized(obj) {
obj.wait();
线程的执行中,先用 synchronized 获得了这个对象的 Monitor(对应于 locked &0xef63beb8& )。当执行到 obj.wait(), 线程即放弃了 Monitor的所有权,进入 “wait set”队列(对应于 waiting on &0xef63beb8& )。
往往在你的程序中,会出现多个类似的线程,他们都有相似的 DUMP信息。这也可能是正常的。比如,在程序中,有多个服务线程,设计成从一个队列里面读取请求数据。这个队列就是 lock以及 waiting on的对象。当队列为空的时候,这些线程都会在这个队列上等待,直到队列有了数据,这些线程被 Notify,当然只有一个线程获得了 lock,继续执行,而其它线程继续等待。3. JDK 5.0 的 lock
上面我们提到如果 synchronized和 monitor机制运用不当,可能会造成多线程程序的性能问题。在 JDK 5.0中,引入了 Lock机制,从而使开发者能更灵活的开发高性能的并发多线程程序,可以替代以往 JDK中的 synchronized和 Monitor的 机制。但是,要注意的是,因为 Lock类只是一个普通类, JVM无从得知 Lock对象的占用情况,所以在线程 DUMP中,也不会包含关于 Lock的信息, 关于死锁等问题,就不如用 synchronized的编程方式容易识别。4.案例分析
在多线程程序的编写中,如果不适当的运用同步机制,则有可能造成程序的死锁,经常表现为程序的停顿,或者不再响应用户的请求。比如在下面这个示例中,是个较为典型的死锁情况:
"Thread-1" prio=5 tid=0x00acc490 nid=0xe50 waiting for monitor entry [0x02d3f000
..0x02d3fd68]
at deadlockthreads.TestThread.run(TestThread.java:31)
- waiting to lock &0x22c19f18& (a java.lang.Object)
- locked &0x22c19f20& (a java.lang.Object)
"Thread-0" prio=5 tid=0x00accdb0 nid=0xdec waiting for monitor entry [0x02cff000
..0x02cff9e8]
at deadlockthreads.TestThread.run(TestThread.java:31)
- waiting to lock &0x22c19f20& (a java.lang.Object)
- locked &0x22c19f18& (a java.lang.Object)
在 JAVA 5中加强了对死锁的检测。线程 Dump中可以直接报告出 Java级别的死锁,如下所示:
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x (object 0x22c19f18, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x (object 0x22c19f20, a java.lang.Object),
which is held by "Thread-1"
热锁,也往往是导致系统性能瓶颈的主要因素。其表现特征为,由于多个线程对临界区,或者锁的竞争,可能出现:
* 频繁的线程的上下文切换:从操作系统对线程的调度来看,当 线程在等待资源而阻塞的时候,操作系统会将之切换出来,放到等待的队列,当线程获得资源之后,调度算法会将这个线程切换进去,放到执行队列中。
* 大量的系统调用:因为线程的上下文切换,以及热锁的竞争,或 者临界区的频繁的进出,都可能导致大量的系统调用。
* 大部分 CPU开销用在 “系统态 ”:线程上下文切换,和系统调用,都会导致 CPU在 “系统态 ”运行,换而言之,虽然系统很忙碌,但是 CPU用在 “用户态 ”的比例较小,应用程序得不到充分的 CPU资源。
* 随着 CPU数目的增多,系统的性能反而下降。因为 CPU数目多,同 时运行的线程就越多,可能就会造成更频繁的线程上下文切换和系统态的 CPU开销,从而导致更糟糕的性能。
上面的描述,都是一个 scalability(可扩展性)很差的系统的表现。从整体的性能指标看,由于线程热锁的存在,程序的响应时间会变长,吞吐量会降低。& /span&
那么,怎么去了解 “热锁 ”出现在什么地方呢?一个重要的方法还是结合操作系统的各种工具观察系统资源使用状况,以及收集 Java线程的 DUMP信息,看线程都阻塞在什么方法上,了解原因,才能找到对应的解决方法。
我们曾经遇到过这样的例子,程序运行时,出现了以上指出的各种现象,通过观察操作系统的资源使用统计信息,以及线程 DUMP信息,确定了程序中热锁的存在,并发现大多数的线程状态都是 Waiting for monitor entry或者 Wait on monitor,且是阻塞在压缩和解压缩的方法上。后来采用第三方的压缩包 javalib替代 JDK自带的压缩包后,系统的性能提高了几倍。
浏览 129438
浏览: 524091 次
来自: 杭州
非常赞,帮助理解了问题。今天也是遇到了这样的问题
请问楼主,新增一个broker的话应该怎么做?给新的broke ...
此文:有意义!
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'}

我要回帖

更多关于 wait和block 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信