多线程ThreadDump分析&&Java对象的锁池和等待池&&线程的状态分析

获取日志

jps 或 ps –ef | grep java (获取PID)
jstack pid | tee -a jstack.log(获取ThreadDump),tee 输出到文件中同时打印到屏幕上
top -H -p 139626 查看该进程中线程的cpu使用率和内存使用率

$ top -H -p 139626
top - 14:10:30 up 367 days, 23:23,  1 user,  load average: 3.05, 3.33, 9.76
Threads: 103 total,   0 running, 103 sleeping,   0 stopped,   0 zombie
%Cpu(s): 14.1 us,  0.8 sy,  0.0 ni, 85.0 id,  0.0 wa,  0.0 hi,  0.1 si,  0.0 st
KiB Mem : 26385808+total, 85839312 free, 61438876 used, 11657990+buff/cache
KiB Swap:        0 total,        0 free,        0 used. 19674998+avail Mem 

   PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                                               
139743 data  20   0 10.110g 2.637g  12868 S  1.3  1.0   2251:34 java                                                                                                                                  
139744 data  20   0 10.110g 2.637g  12868 S  1.3  1.0   2251:35 java                                                                                                                                  

dump信息

  • 代码
public class ObjectWaiteDemo {
    private static Object obj = new Object();
    public static void main(String[] args){
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized(obj) {
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.start();
    }
}

jps
jstack pid

  • dump信息
    需要倒着看,进入同步代码块需要获取对象锁 - locked <0x000000076b60dba0>,然后执行at java.lang.Object.wait(Object.java:502)方法,释放锁之后线程处于 - waiting on <0x000000076b60dba0>
    in Object.wait()
2020-05-11 14:35:50
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode):
"Service Thread" 11 daemon prio=9 os_prio=0 tid=0x000000002124a800 nid=0x9b68 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
【主线程】
"DestroyJavaVM" 13 prio=5 os_prio=0 tid=0x0000000004e17000 nid=0x3d54 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
【自定义线程】
"Thread-0" 12 prio=5 os_prio=0 tid=0x0000000021423000 nid=0x9810 in Object.wait() [0x000000002213f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076b60dba0> (a java.lang.Object)
        at java.lang.Object.wait(Object.java:502)
        at shengsiyuan.jdk8.jvmTuning.ObjectWaiteDemo$1.run(ObjectWaiteDemo.java:14)
        - locked <0x000000076b60dba0> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)
1. 线程名称:Thread-0;线程类型:daemon;优先级: 10,默认是5
2. JVM线程id:tid=0x0000000021423000 ,JVM内部线程的唯一标识(通过java.lang.Thread.getId()获取,通常用自增方式实现)
3. 对应系统线程id(NativeThread ID):nid=0x9810 ,和top命令查看的线程pid对应,不过一个是10进制,一个是16进制。(通过命令:top -H -p pid,可以查看该进程的所有线程信息)
4. 线程状态:in Object.wait()
5. 起始栈地址:[0x000000002213f000],对象的内存地址,通过JVM内存查看工具,能够看出线程是在哪儿个对象上等待

线程生命周期

线程状态 RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,NEW,TERMINATED
主要分析线程RUNNABLE,BLOCKED,WAITING,TIMED_WAITING四种状态

  • RUNNABLE:线程处于运行或者等待cpu时间片运行阶段
  • BLOCKED:等待获取对象锁
  • WAITING:处在该线程的状态,正在等待某个事件的发生,只有特定的条件满足,才能获得执行机会。而产生这个特定的事件,通常都是另一个线程。也就是说,如果不发生特定的事件,那么处在该状态的线程一直等待,不能获取执行的机会
    1. A线程调用了obj对象的obj.wait()方法,如果没有线程调用obj.notify或obj.notifyAll,那么A线程就没有办法恢复运行;
    2. 如果A线程调用了LockSupport.park(),没有别的线程调用LockSupport.unpark(A),那么A没有办法恢复运行。
  • TIMED_WAITING:线程进入了WAITING状态,一定要特定的事件发生才能恢复运行;而处在TIMED_WAITING的线程,如果特定的事件发生或者是时间流逝完毕,都会恢复运行

线程关键状态分析

  • Wait on condition && in Object.wait()
    The thread is either sleeping or waiting to be notified by another thread
  1. java.lang.Thread.State: WAITING (parking):一直等那个条件发生;
  2. java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定时的,那个条件不到来,也将定时唤醒自己。
  • Waiting for Monitor Entry
    The thread is waiting to get the lock for an object (some other thread may be holding the lock). This happens if two or more threads try to execute **synchronized code**. Note that the lock is always for an object and not for individual methods.
    image.png
  • 如上图所示
  1. 每一个对象都有且仅有一个 Monitor, 一个对象上的Monitor在某个时刻,只能被一个线程拥有,该线程就是 “ActiveThread”,而其它线程都是 “Waiting Thread”,分别在两个队列Entry ListWait Set里等候。在Entry List中等待的线程状态是Waiting for monitor entry,而在Wait Set中等待的线程状态是in Object.wait()

  2. Java每个对象上面都有锁池和等待池
    src/share/vm/prims/jvm.cpp
    src/share/vm/runtime/synchronizer.hpp
    src/share/vm/runtime/objectMonitor.hpp

  ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL;
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }
  • _EntryList 锁池 线程状态是 Waiting for monitor entry
    存放等待获取该对象锁的线程,参与该对象锁的竞争,然后该线程进入runnable状态
  • _WaitSet 等待池 线程状态是in Object.wait() || waiting on condition
    存放所有在该对象上wait的线程,不参与该对象锁的竞争,等待唤醒(条件唤醒,超时自动唤醒),然后该线程进入runnable状态

状态示例

1.waiting for monitor entry 示例

这1分钟内会t2一直持有object的监视器,另t2线程无法执行处在BLOCKED状态等待获取对象锁

public class BlockedState {
    private static Object object = new Object();

    public static void main(String[] args) {
        Runnable task = new Runnable() {
            @Override
            public void run()
            {
                synchronized (object)
                {
                    long begin = System.currentTimeMillis();

                    long end = System.currentTimeMillis();

                    // 让线程运行1分钟,会一直持有object的监视器
                    while ((end - begin) <= 1 * 60 * 1000) {
                        end = System.currentTimeMillis();
                    }
                }
            }
        };
        new Thread(task, "t1").start();
        new Thread(task, "t2").start();
    }
}
"t2" #13 prio=5 os_prio=0 tid=0x00000000216be000 nid=0x65f4 runnable [0x00000000223de000]
   java.lang.Thread.State: RUNNABLE
        at shengsiyuan.jdk8.jvmTuning.BlockedState$1.run(BlockedState.java:22)
        - locked <0x000000076b60db50> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

"t1" #12 prio=5 os_prio=0 tid=0x00000000216bd800 nid=0x5604 waiting for monitor entry [0x00000000222de000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at shengsiyuan.jdk8.jvmTuning.BlockedState$1.run(BlockedState.java:16)
        - waiting to lock <0x000000076b60db50> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)
2.in Object.wait() 示例
public class WaitingState {
    private static Object object = new Object();
    public static void main(String[] args) {
        Runnable task = new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                        // 进入等待的同时,会进入释放监视器
                        try {
                            object.wait();
                            Thread.sleep(1000*60);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
            }
        };
        new Thread(task, "t1").start();
        new Thread(task, "t2").start();
    }
}
"t2" #13 prio=5 os_prio=0 tid=0x0000000020eff800 nid=0x86c4 in Object.wait() [0x0000000021bff000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076b60db50> (a java.lang.Object)
        at java.lang.Object.wait(Object.java:502)
        at shengsiyuan.jdk8.jvmTuning.WaitingState$1.run(WaitingState.java:21)
        - locked <0x000000076b60db50> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

"t1" #12 prio=5 os_prio=0 tid=0x0000000020f17000 nid=0x90e4 in Object.wait() [0x0000000021afe000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076b60db50> (a java.lang.Object)
        at java.lang.Object.wait(Object.java:502)
        at shengsiyuan.jdk8.jvmTuning.WaitingState$1.run(WaitingState.java:21)
        - locked <0x000000076b60db50> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)


"t2" #13 prio=5 os_prio=0 tid=0x0000000021820000 nid=0x3b98 waiting for monitor entry [0x000000002253f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at shengsiyuan.jdk8.jvmTuning.WaitingState$1.run(WaitingState.java:13)
        - waiting to lock <0x000000076b60db50> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

"t1" #12 prio=5 os_prio=0 tid=0x000000002181f000 nid=0x9268 waiting on condition [0x000000002243f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at shengsiyuan.jdk8.jvmTuning.WaitingState$1.run(WaitingState.java:20)
        - locked <0x000000076b60db50> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

3.waiting on condition 示例
public class TimedWaitingState {
    // java的显示锁,类似java对象内置的监视器
    private static Lock lock = new ReentrantLock();
    // 锁关联的条件队列(类似于object.wait)
    private static Condition condition = lock.newCondition();
    public static void main(String[] args)
    {
        Runnable task = new Runnable() {

            @Override
            public void run() {
                // 加锁,进入临界区
                lock.lock();
                try {
                    System.out.println("进入临届区");
                    condition.await(5, TimeUnit.MINUTES);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 解锁,退出临界区
                lock.unlock();
                System.out.println("退出临届区");
            }
        };

        new Thread(task, "t1").start();
        new Thread(task, "t2").start();
    }
}
"t2" #13 prio=5 os_prio=0 tid=0x0000000021629000 nid=0x77bc waiting on condition [0x00000000224be000]
   java.lang.Thread.State: TIMED_WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076b616028> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2163)
        at shengsiyuan.jdk8.jvmTuning.TimedWaitingState$1.run(TimedWaitingState.java:26)
        at java.lang.Thread.run(Thread.java:748)

"t1" #12 prio=5 os_prio=0 tid=0x0000000021628000 nid=0x2240 waiting on condition [0x00000000223bf000]
   java.lang.Thread.State: TIMED_WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076b616028> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2163)
        at shengsiyuan.jdk8.jvmTuning.TimedWaitingState$1.run(TimedWaitingState.java:26)
        at java.lang.Thread.run(Thread.java:748)

  • 分析:
    ReentrantLock同sync用法一样,性能上也没有太大差距,t2线程获取对象锁,进入临届区之后调用await释放对象锁,t1线程过程和t2一样,最后状态都处于TIMED_WAITING 状态,等待condition.notify()或者超时唤醒

wait/sleep/yield/join 的区别

  • thread状态转移图
  1. wait 实例方法,暂停线程执行,调用需要先在同步方法中获取对象锁,调用之后会释放对象锁,只有在其它线程需要获取对象锁然后调用该实例方法notify被唤醒之后才能在被wait的地方接着执行
  2. sleep 静态方法,短时间暂停当前线程执行,作用在当前线程上,不会释放对象锁,也不需要在同步方法中调用,等超时之后自动唤醒notify接着执行
  3. yield 静态方法,释放当前线程cpu时间片,至于哪个线程会执行,取决于调度器,当前线程进入ready状态,随时可能再被调度
  4. join 实例方法,底层实现通过wait()方式实现,暂停主线程执行,等待子线程执行完,主线程再执行(自动唤醒notify)
    以上共同点:都会释放cpu时间片,释放cpu资源
    image.png

问题

sleep/yield/join方法位于Thread类中
wait/notify/notifyAll方法的位于Object类中,作用是实现线程间的协作,那么为什么不放在Thread类中?
在调用wait(), notify()或notifyAll()的时候,必须先获得锁,且状态变量须由该锁保护

  • 简答:
    wait/notify/notifyAll方法位于Object中,也就相当于所有类都包含这三个方法
    这个和waite实现原理有关系,wait等待其实是对象锁,由于Java中的每一个对象都有一个内置的锁对象,自然所有的类都理应有wait/notify/notifyAll方法
    join 代码
public class JoinTest implements Runnable{
    @Override
    public void run() {

        try {
            System.out.println(Thread.currentThread().getName() + " start-----");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " end------");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * join(long millis)方法的实现,可以看出join方法就是通过wait方法来将线程的阻塞,
     * 如果join的线程还在执行,则将当前线程阻塞起来,直到join的线程执行完成,当前线程才能执行。
     * 不过有一点需要注意,这里的join只调用了wait方法,却没有对应的notify方法,原因是Thread的start方法中做了相应的处理,
     * 所以当join的线程执行完成以后,会自动唤醒主线程继续往下执行
     * @param args
     */
    public static void main(String[] args) throws InterruptedException {
        for (int i=0;i<5;i++) {
            Thread test = new Thread(new JoinTest());
            test.start();
            //join方法的作用是父线程(主线程)等待子线程执行完成后再执行,换句话说就是将异步执行的线程合并为同步的线程。
            // JDK中提供三个版本的join方法,其实现与wait方法类似,
            // join()方法实际上执行的join(0),而join(long millis, int nanos)也与wait(long millis, int nanos)的实现方式一致
            test.join();
        }
        //join 可以阻塞主线程,直到子线程都执行完
        System.out.println("Finished~~~");
    }
}

参考
https://juejin.im/post/5b31b510e51d4558a426f7e9#heading-8

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 227,022评论 6 528
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 97,760评论 3 412
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 174,687评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,318评论 1 307
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,112评论 6 405
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,645评论 1 320
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,724评论 3 435
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 41,885评论 0 285
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,387评论 1 330
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,368评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,506评论 1 365
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,063评论 5 355
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,759评论 3 343
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,150评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,414评论 1 281
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,080评论 3 386
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,494评论 2 370