java线程堆栈 分析

作为一名合格的java程序员,对线程堆栈的了解是必须的。此文将 三位一体,全方位覆盖。请移此笃诚瞧之。


一. java线程堆栈概述

线程堆栈能用来干嘛?

线程堆栈就是线程的执行轨迹。可以分析出进程中每个线程的当前状态,是否等待锁,等待哪个锁等。还可以找出代码调用行数。


请看下面一个堆栈实例

"resin-22129" daemon prio=10 tid=0x00007fbe5c34e000 nid=0x4cb1 waiting on condition [0x00007fbe4ff7c000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:315)
    at com.caucho.env.thread2.ResinThread2.park(ResinThread2.java:196)
    at com.caucho.env.thread2.ResinThread2.runTasks(ResinThread2.java:147)
    at com.caucho.env.thread2.ResinThread2.run(ResinThread2.java:118)
  • resin-22129 : 线程名称。
  • daemon : 线程类型:线程分为守护线程 (daemon) 和非守护线程 (non-daemon) 两种。
  • prio :线程优先级:默认为5,数字越大优先级越高。
  • tid :JVM线程的id,通过Thread.getId()获取。
  • nid : 系统线程id:对应的系统线程id,可以通过 top命令进行查看,线程id是十六进制的形式。
  • waiting on condition : 操作系统线程状态,跟java还不一样,具体含义见下面分析。
  • 0x00007fbe4ff7c000 : 起始栈地址:线程堆栈调用的起始内存地址;
  • java.lang.Thread.State: WAITING (parking) : java线程的状态,下面分析。
  • 线程调用代码跟踪


二. Linux线程简介

我们回归原始,在linux上写一段java程序,然后javac编译,java运行:

public class A{
        public static void main(String[] args)throws InterruptedException {
                while(true) {
                        Thread.sleep(10);
                }
        }
}

待程序运行后,我们新开窗口用ps找出进程pid

[root@VM-0-3-centos ~]# ps -ef |grep java
root     25791 25350  0 18:11 pts/0    00:00:00 java A

pid为 25791,然后我们看下进程的线程情况

[root@VM-0-3-centos ~]# top -Hp 25791
top - 18:13:42 up 172 days, 3 min,  2 users,  load average: 0.01, 0.03, 0.05
Threads:   9 total,   0 running,   9 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.7 us,  0.7 sy,  0.0 ni, 98.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  1882192 total,    81000 free,   779520 used,  1021672 buff/cache
KiB Swap:        0 total,        0 free,        0 used.   937196 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                                          
25792 root      20   0  324360  12264   6312 S  0.3  0.7   0:00.37 java                                                                                                                             
25799 root      20   0  324360  12264   6312 S  0.3  0.7   0:00.06 java                                                                                                                             
25791 root      20   0  324360  12264   6312 S  0.0  0.7   0:00.00 java                                                                                                                             
25793 root      20   0  324360  12264   6312 S  0.0  0.7   0:00.00 java                                                                                                                             
25794 root      20   0  324360  12264   6312 S  0.0  0.7   0:00.00 java                                                                                                                             
25795 root      20   0  324360  12264   6312 S  0.0  0.7   0:00.00 java                                                                                                                             
25796 root      20   0  324360  12264   6312 S  0.0  0.7   0:00.00 java                                                                                                                             
25797 root      20   0  324360  12264   6312 S  0.0  0.7   0:00.00 java                                                                                                                             
25798 root      20   0  324360  12264   6312 S  0.0  0.7   0:00.00 java

在linux中, 其实线程也是进程模拟的, 与普通进程最大区别在于: 线程间通过mmap共享一段内存 。而普通进程内存空间完全是两份独立的。

sleeping线程状态(跟进程一样)

1. running 正在运行的进程数
2. sleeping 睡眠的进程数
3. stopped 停止的进程数
4. zombie 僵尸进程数

%Cpu(s):0.7 us,0.7 sy CPU占用情况

  • 0.7 us : 用户态占用cpu。
  • 0.7 sy : 为内核态占用cpu。


用户态和内核态

在CPU的所有指令中,有一些指令是非常危险的,如果错用,将导致整个系统崩溃。所以,CPU将指令分为 特权指令和非特权指令 ,对于那些危险的指令,只允许操作系统及其相关模块使用( 内核态 ),普通的应用程序只能使用那些不会造成灾难的指令( 用户态 )。其实就是Linux操作系统将权限等级分为了2个等级,分别就是内核态和用户态。

当我们访问外围设备,例如硬盘,网卡等时,进程就会切换成内核态运行。

我们现在测试下下面代码

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class A {
        public static void main(String[] args) throws InterruptedException, IOException {
                FileOutputStream fis = new FileOutputStream(new File("1.txt")) ;
                while(true) {
                        fis.write("1111\r\n".getBytes());
                        fis.flush();
                }
        }
}

程序循环去写磁盘,上面知识我们了解到,写磁盘肯定要从用户态切换成内核态。于是我们运行上面程序,然后top -Hp看下

top - 19:07:19 up 172 days, 57 min,  2 users,  load average: 0.59, 0.16, 0.09
Threads:   9 total,   1 running,   8 sleeping,   0 stopped,   0 zombie
%Cpu(s): 29.4 us, 70.6 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  1882192 total,    82716 free,   781576 used,  1017900 buff/cache
KiB Swap:        0 total,        0 free,        0 used.   935188 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                                          
 1716 root      20   0  324496  16424   6408 R 93.8  0.9   0:22.68 java                                                                                                                             
 1715 root      20   0  324496  16424   6408 S  0.0  0.9   0:00.00 java                                                                                                                             
 1717 root      20   0  324496  16424   6408 S  0.0  0.9   0:00.04 java                                                                                                                             
 1718 root      20   0  324496  16424   6408 S  0.0  0.9   0:00.00 java                                                                                                                             
 1719 root      20   0  324496  16424   6408 S  0.0  0.9   0:00.00 java                                                                                                                             
 1720 root      20   0  324496  16424   6408 S  0.0  0.9   0:00.00 java                                                                                                                             
 1721 root      20   0  324496  16424   6408 S  0.0  0.9   0:00.00 java                                                                                                                             
 1722 root      20   0  324496  16424   6408 S  0.0  0.9   0:00.00 java                                                                                                                             
 1723 root      20   0  324496  16424   6408 S  0.0  0.9   0:00.00 java
  

我们惊奇发现 %Cpu(s):29.4 us,70.6 sy ,我们的内核态cpu占用了高达70.6 多。

这个是我们已知 代码 是写磁盘 造成的,一般线上你可是什么都不知道的。下面教你用个strace命令 来找出上面到底是哪里问题。

[root@VM-0-3-centos ~]# strace -c -f -p 7918
....
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 77.23    5.442390       15684       347       158 futex
 15.59    1.098267           3    339589           write
  7.13    0.502539      167513         3         2 restart_syscall
  0.05    0.003348           3       940           clock_gettime
  0.00    0.000076           3        20           mprotect
  0.00    0.000045           2        20           gettimeofday
------ ----------- ----------- --------- --------- ----------------
100.00    7.046665                340919       160 total

###############
需要按Ctrl+C 才能得出显示结果

strace可以监控一段时间的系统调用情况,系统调用就是 Linux 内核提供的一组用户进程与内核进行交互的接口。发生系统调用就是进入了内核态。我们发现有339589次系统调用都是在write上,也就是我们的写文件。(还有很多系统调用,读者可以自行研究)

但是我们的write到底写了些什么呢,于是下面命令可以查看系统详细调用情况。

strace -tt -f -o output.log -p 7918

由于是监控的进程7918的所有系统调用,比较多。所以输出到文件里面,打开文件:

[pid 11078] 10:17:37.799978 write(4, "1111\r\n", 6) = 6
[pid 11078] 10:17:37.800052 write(4, "1111\r\n", 6) = 6
[pid 11078] 10:17:37.800176 write(4, "1111\r\n", 6) = 6
[pid 11078] 10:17:37.800234 write(4, "1111\r\n", 6) = 6
[pid 11078] 10:17:37.800286 write(4, "1111\r\n", 6) = 6
[pid 11078] 10:17:37.800351 write(4, "1111\r\n", 6) = 6
[pid 11078] 10:17:37.800420 write(4, "1111\r\n", 6) = 6
[pid 11078] 10:17:37.800489 write(4, "1111\r\n", 6) = 6
................

可以看到调用了什么系统函数,和参数。很详细了。

天啊, 我在讲些什么,好像有点跑偏了,跟主题没一点关系啊,但我已没有回头之路,只好硬着头皮往下讲。

Linux线程堆栈

我们打印下linux的线程堆栈(都是上面那个Demo java程序)

[root@VM-0-3-centos ~]# top
11781 root      20   0  324496  14752   4532 S 99.3  0.8   6:49.15 java

[root@VM-0-3-centos ~]# top -Hp 11781
  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                                          
11782 root      20   0  324496  14752   4532 R 98.3  0.8   7:36.64 java                                                                                                                             
11784 root      20   0  324496  14752   4532 S  0.3  0.8   0:00.83 java                                                                                                                             
11781 root      20   0  324496  14752   4532 S  0.0  0.8   0:00.00 java


[root@VM-0-3-centos ~]# pstack 11782
Thread 1 (process 11782):
#0  0xf6ad2492 in writeBytes () from /usr/local/app/java8/jre/lib/i386/libjava.so
#1  0xf6ac8e2d in Java_java_io_FileOutputStream_writeBytes () from /usr/local/app/java8/jre/lib/i386/libjava.so
#2  0xf49a53c0 in ?? ()
#3  0xf49abc81 in ?? ()
#4  0xf6e5315f in JavaCalls::call_helper(JavaValue*, methodHandle*, JavaCallArguments*, Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#5  0xf6fe5a49 in os::os_exception_wrapper(void (*)(JavaValue*, methodHandle*, JavaCallArguments*, Thread*), JavaValue*, methodHandle*, JavaCallArguments*, Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#6  0xf6e5172f in JavaCalls::call(JavaValue*, methodHandle, JavaCallArguments*, Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#7  0xf6e9803f in jni_invoke_static(JNIEnv_*, JavaValue*, _jobject*, JNICallType, _jmethodID*, JNI_ArgumentPusher*, Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#8  0xf6ea2f6f in jni_CallStaticVoidMethod () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#9  0xf77a51bf in JavaMain () from /usr/local/app/java8/bin/../lib/i386/jli/libjli.so
#10 0xf77b9bbc in start_thread () from /lib/libpthread.so.0
#11 0xf76cc0de in clone () from /lib/libc.so.6

可以看出我们java在操作系统层面的调用情况。



三. java的线程堆栈

  • java线程的几种状态
// Thread.State 源码
public enum State {
    NEW,
    RUNNABLE, // 就绪和 运行
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

初始状态(NEW)

new一个实例出来,线程就进入了初始状态,但是我们dump线程堆栈是看不到的。

就绪状态(RUNNABLE)

调用线程的start()方法,此线程进入就绪状态,等待运行。

==================上面2种状态了解下就行了=================

运行中状态(RUNNABLE)

线程正在拿到cpu运行状态,是唯一一个占CPU的状态。也有可能不占cpu,当调用socket的accept,等待socket连接或者 等待读取网络io数据时,线程是RUNNABLE的,但是程序会hang住,且不耗cpu。

有这么一段程序启动后,cpu占用一直是90%多,如果是你怎么定位?

public static void main(String[] args) throws InterruptedException, IOException {
		Thread t = new Thread("hadluo") {
			@Override
			public void run() {
				while(true) {
					String a = "";
					int b = 0;
					a = a + (b++);
				}
			}
		};
		t.start();
		Thread.sleep(100000);
}

cpu 高,一定是线程执行频繁,要么是用户态执行频繁要么是内核态执行频繁。我们首先来定位下是哪个线程搞的鬼?

######################## jps 定位到 进程pid
[root@VM-0-3-centos ~]# jps
53475 A

######################## top 找出cpu高线程pid
[root@VM-0-3-centos ~]# top -Hp 53475
Threads:  10 total,   1 running,   9 sleeping,   0 stopped,   0 zombie
%Cpu(s):100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  1882192 total,    86628 free,   783316 used,  1012248 buff/cache
KiB Swap:        0 total,        0 free,        0 used.   937040 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                                          
53476 root      20   0  324688  16692   6416 S 93.3  0.9   3:26.13 java                                                                                                                             
53477 root      20   0  324688  16692   6416 R  6.7  0.9   0:12.88 java                                                                                                                             
53478 root      20   0  324688  16692   6416 S  0.0  0.9   0:00.00 java                                                                                                                             
53479 root      20   0  324688  16692   6416 S  0.0  0.9   0:00.05 java

我们发现 都是我们的us 用户态用了,占用最高的是我们的53476线程pid。由于java线程堆栈里面只显示pid的16进制,于是我们换成16进制。

[root@VM-0-3-centos ~]# printf "%x\n" 53476
0xd0e4 

[root@VM-0-3-centos ~]# jstack 18725 |grep 4925 -A 20
"hadluo" #11 prio=5 os_prio=0 tid=0x000000001dec5000 nid=0xd0e4 runnable [0x000000001fa4e000]
   java.lang.Thread.State: RUNNABLE
        at TestHashedWheelTimer$1.run(TestHashedWheelTimer.java:11)

   Locked ownable synchronizers:
        - None
.....

发现代码在TestHashedWheelTimer 的11行。

java线程和系统线程

java线程start方法最终都追踪到native start0方法上,该方法通过jni机制,首先在C++层面创建了JavaThread对象,然后在JavaThread的构造里面通过c函数 pthread_create 创建了系统级别的线程。 因此java线程和系统线程是一个一对一的关系。

jvm层源码解析

罗政:Java Thread源码分析

BLOCKED状态

受阻塞并且正在等待监视器的某一线程的线程状态。也就是进入synchronized时拿不到对象锁阻塞的状态。

请看实例:

public static void main(String[] args) throws ClassNotFoundException, InterruptedException {
    final Object lock = new Object();
    Thread t1 = new Thread() {
        @Override
        public void run() {
            synchronized (lock) {
                while (true) {
                }
            }

        }
    };
    t1.start();
    Thread.sleep(1000);
    Thread t2 = new Thread() {
        @Override
        public void run() {
            synchronized (lock) {
                System.err.println("t2 获取到锁");
            }

        }
    };
    t2.start();
}

获取堆栈:

"Thread-1" #8 prio=5 os_prio=0 tid=0xf6995400 nid=0x1a00 waiting for monitor entry [0xe3a67000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at a$2.run(a.java:24)
	- waiting to lock <0xe4852528> (a java.lang.Object)

"Thread-0" #7 prio=5 os_prio=0 tid=0xf6994400 nid=0x19ff runnable [0xe3ab8000]
   java.lang.Thread.State: RUNNABLE
	at a$1.run(a.java:12)
	- locked <0xe4852528> (a java.lang.Object)

Thread-1 是BLOCKED状态,也就是synchronized块里等待对象锁,等待锁的地址为 0xe4852528 , 恰好线程 Thread-0 锁的就是0xe4852528 。 如果上面这个两个线程都是BLOCKED,则确认就是发生了死锁了(例子我就不举了)。

接下来来看下操作系统是什么状态?

0x1a00 的十进制 是: 6656 , 得出线程pid是6656

[root@VM-0-3-centos ~]# cat /proc/6656/status
Name:	java
Umask:	0022
State:	S (sleeping)
....

#####查看6656 线程 堆栈
[root@VM-0-3-centos ~]# pstack 6656
Thread 1 (process 6656):
#0  0xf776d430 in __kernel_vsyscall ()
#1  0xf7753bb4 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib/libpthread.so.0
#2  0xf6f81582 in os::PlatformEvent::park(long long) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#3  0xf6f6c24d in ObjectMonitor::EnterI(Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#4  0xf6f6e3a8 in ObjectMonitor::enter(Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#5  0xf701ffb5 in ObjectSynchronizer::fast_enter(Handle, BasicLock*, bool, Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#6  0xf6ddd68b in InterpreterRuntime::monitorenter(JavaThread*, BasicObjectLock*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#7  0xf4912fdf in ?? ()
#8  0xf49003d9 in ?? ()
#9  0xf6de915f in JavaCalls::call_helper(JavaValue*, methodHandle*, JavaCallArguments*, Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#10 0xf6f7ba49 in os::os_exception_wrapper(void (*)(JavaValue*, methodHandle*, JavaCallArguments*, Thread*), JavaValue*, methodHandle*, JavaCallArguments*, Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#11 0xf6de957c in JavaCalls::call_virtual(JavaValue*, KlassHandle, Symbol*, Symbol*, JavaCallArguments*, Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#12 0xf6de99bb in JavaCalls::call_virtual(JavaValue*, Handle, KlassHandle, Symbol*, Symbol*, Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#13 0xf6e7612d in thread_entry(JavaThread*, Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#14 0xf705a519 in JavaThread::thread_main_inner() () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#15 0xf705a70e in JavaThread::run() () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#16 0xf6f828b9 in java_start(Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#17 0xf774fbbc in start_thread () from /lib/libpthread.so.0
#18 0xf76620de in clone () from /lib/libc.so.6

结论:

  • java在synchronized块等待获取对象锁时,线程变成BLOCKED状态。
  • java线程BLOCKED,操作系统为sleeping状态。
  • 从线程堆栈来看已经涉及到__kernel_vsyscall () 内核系统调用,也就发生了用户态到核心态切换了。
  • 底层最终是通过调用pthread_cond_timedwait 来实现线程锁等待,而pthread_cond_timedwait 的底层是通过linux futex机制实现的。


linux futex机制(类似java的锁升级)

Futex按英文翻译过来就是快速用户空间互斥体。其设计思想其实 不难理解,在传统的Unix系统中,System V IPC(inter process communication),如 semaphores, msgqueues, sockets还有文件锁机制(flock())等进程间同步机制都是对一个内核对象操作来完成的,这个内核对象对要同步的进程都是可见的,其提供了共享 的状态信息和原子操作。当进程间要同步的时候必须要通过系统调用(如semop())在内核中完成。可是经研究发现,很多同步是无竞争的,即某个进程进入 互斥区,到再从某个互斥区出来这段时间,常常是没有进程也要进这个互斥区或者请求同一同步变量的。但是在这种情况下,这个进程也要陷入内核去看看有没有人 和它竞争,退出的时侯还要陷入内核去看看有没有进程等待在同一同步变量上。这些不必要的系统调用(或者说内核陷入)造成了大量的性能开销。为了解决这个问 题,Futex就应运而生,Futex是一种用户态和内核态混合的同步机制。首先,同步的进程间通过mmap共享一段内存,futex变量就位于这段共享 的内存中且操作是原子的,当进程尝试进入互斥区或者退出互斥区的时候,先去查看共享内存中的futex变量,如果没有竞争发生,则只修改futex,而不 用再执行系统调用了。当通过访问futex变量告诉进程有竞争发生,则还是得执行系统调用去完成相应的处理(wait 或者 wake up)。简单的说,futex就是通过在用户态的检查,(motivation)如果了解到没有竞争就不用陷入内核了,大大提高了low-contention时候的效率。 Linux从2.5.7开始支持Futex。

上文出此,想了解详细的话请看 :

Linux Futex的设计与实现



WAITING状态

某一等待线程的线程状态。请看实例:

public static void main(String[] args) throws ClassNotFoundException, InterruptedException {
    final Object lock = new Object();
    Thread t2 = new Thread() {
        @Override
        public void run() {
            synchronized (lock) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                }
            }

        }
    };
    t2.start();
}

请看线程堆栈

"Thread-0" #7 prio=5 os_prio=0 tid=0xf6994000 nid=0x4ed6 in Object.wait() [0xe3b09000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0xe4851890> (a java.lang.Object)
	at java.lang.Object.wait(Object.java:502)
	at a$1.run(a.java:13)
	- locked <0xe4851890> (a java.lang.Object)

可以很清晰的看到in Object.wait() , java.lang.Thread.State:WAITING 。

操作系统级别堆栈

[root@VM-0-3-centos ~]# pstack 20182
Thread 1 (process 20182):
#0  0xf779f430 in __kernel_vsyscall ()
#1  0xf77857cc in pthread_cond_wait@@GLIBC_2.3.2 () from /lib/libpthread.so.0
#2  0xf6fae224 in os::PlatformEvent::park() () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#3  0xf6fa1316 in ObjectMonitor::wait(long long, bool, Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#4  0xf7051b63 in ObjectSynchronizer::wait(Handle, long long, Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#5  0xf6ea7c7d in JVM_MonitorWait () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#6  0xf490befa in ?? ()
#7  0xf49040f4 in ?? ()
#8  0xf49040f4 in ?? ()
#9  0xf49003d9 in ?? ()
#10 0xf6e1b15f in JavaCalls::call_helper(JavaValue*, methodHandle*, JavaCallArguments*, Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#11 0xf6fada49 in os::os_exception_wrapper(void (*)(JavaValue*, methodHandle*, JavaCallArguments*, Thread*), JavaValue*, methodHandle*, JavaCallArguments*, Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#12 0xf6e1b57c in JavaCalls::call_virtual(JavaValue*, KlassHandle, Symbol*, Symbol*, JavaCallArguments*, Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#13 0xf6e1b9bb in JavaCalls::call_virtual(JavaValue*, Handle, KlassHandle, Symbol*, Symbol*, Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#14 0xf6ea812d in thread_entry(JavaThread*, Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#15 0xf708c519 in JavaThread::thread_main_inner() () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#16 0xf708c70e in JavaThread::run() () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#17 0xf6fb48b9 in java_start(Thread*) () from /usr/local/app/java8/jre/lib/i386/client/libjvm.so
#18 0xf7781bbc in start_thread () from /lib/libpthread.so.0
#19 0xf76940de in clone () from /lib/libc.so.6

跟 synchronized 没什么区别。请在看一个实例:

public static void main(String[] args) throws ClassNotFoundException, InterruptedException {
    Thread t2 = new Thread() {
        @Override
        public void run() {
           LockSupport.park();
        }
    };
    t2.start();
}

java线程堆栈

"Thread-0" #7 prio=5 os_prio=0 tid=0xf6994000 nid=0x52c6 waiting on condition [0xe3ab8000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
	at a$1.run(a.java:11)

发现 是调用sun.misc.Unsafe.park 实现的。

总结:

  • LockSupport.park(); 和 Object.wait 都使 线程变成 WAITING
  • 上述两种都要执行系统调用__kernel_vsyscall()。


TIMED_WAITING 状态

指定了等待时间的某一等待线程的线程状态。以下操作会进入此状态:

  • Thread.sleep 方法。
  • 指定超时值的 Object.wait 方法。
  • 指定超时值的 Thread.join 方法。
  • LockSupport.parkNanos。
  • LockSupport.parkUntil。

TERMINATED

线程处于终止状态。

其实终归到底都是在与linux的futex , 大家有兴趣可以了解下。

强烈推荐一个 进阶 JAVA架构师 的博客

Java架构师修炼

支付宝打赏 微信打赏

如果文章对您有帮助,您可以鼓励一下作者