一、线程的生命周期及五种基本状态
关于Java中线程的生命周期,首先看一下下面这张较为经典的图:
上图中基本上囊括了Java中多线程各重要知识点。掌握了上图中的各知识点,Java中的多线程也就基本上掌握了。主要包括:
Java线程具有七种基本状态
新建状态(New):至今尚未启动的线程的状态。线程刚被创建,但尚未启动。如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
无限期等待(Waiting):位于对象等待池中的阻塞状态(Blocked in object’s wait pool):当线程处于运行状态时,如果执行了某个对象的wait()方法,Java虚拟机就会把线程放到这个对象的等待池中,这涉及到“线程通信”的内容。处于这种状态的线程不会被分配处理器执行时间,它们要等待被其他线程显示唤醒。以下方法会让线程陷入无限期的等待状态:
没有设置timeout参数的Object::wait()方法
没有设置timeout参数的Thread::join()方法
LockSupport::park()方法
限期等待(Timed Waiting):处于这种状态的线程也不会被分配处理器执行时间,不过无须等待其他线程显示唤醒,在一定时间后它们由系统自动唤醒。以下方法会让线程进入期限等待状态:
Thread::sleep()方法
设置了timeout参数的Object::wait()方法
设置了timeout参数的Thread::join()方法
LockSupport::parkNanos()方法
LockSupport::parkUntil()方法
阻塞状态(Blocked):处于运行状态中的线程由于某种(当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,Java虚拟机就会把这个线程放到这个对象的锁池中,这涉及到“线程同步”的内容。【线程在获取synchronized同步锁失败(因为锁被其它线程所占用)】)原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。
“阻塞状态”与“等待状态”的区别:“阻塞状态”在等待着获取一个排它锁,这个事件将在另一个线程放弃这个锁的时候发生;而“等待状态”则是在等待一段时间,或者唤醒动作发生。在程序进入同步区域的时候,线程就会进入阻塞状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
JVM线程运行状态 (JVM Thread Status)
至今尚未启动的线程的状态。线程刚被创建,但尚未启动。
可运行线程的线程状态。线程正在JVM中执行,有可能在等待操作系统中的其他资源,比如处理器。
受阻塞并且正在等待监视器的某一线程的线程状态。处于受阻塞状态的某一线程正在等待监视器锁,以便进入一个同步的块/方法,或者在调用 Object.wait 之后再次进入同步的块/方法。在Thread Dump日志中通常显示为 java.lang.Thread.State: BLOCKED (on object monitor) 。
某一等待线程的线程状态。线程正在无期限地等待另一个线程来执行某一个特定的操作,线程因为调用下面的方法之一而处于等待状态:
不带超时的 Object.wait 方法,日志中显示为 java.lang.Thread.State: WAITING (on object monitor)
不带超时的 Thread.join 方法
LockSupport.park 方法,日志中显示为 java.lang.Thread.State: WAITING (parking)
指定了等待时间的某一等待线程的线程状态。线程正在等待另一个线程来执行某一个特定的操作,并设定了指定等待的时间,线程因为调用下面的方法之一而处于定时等待状态:
Thread.sleep 方法
指定超时值的 Object.wait 方法
指定超时值的 Thread.join 方法
LockSupport.parkNanos
LockSupport.parkUntil
线程处于终止状态。
根据Java Doc中的说明,在给定的时间上,一个只能处于上述的一种状态之中,并且这些状态都是JVM的状态,跟操作系统中的线程状态无关。
JAVA虚拟机启动程序步骤:
(1) Main是启动时候的主线程,即程序入口
(2) 在main函数结束后,虚拟机会自动启动一个DestroyJavaVM线程,该线程会等待所有user thread 线程结束后退出(即,只剩下daemon 线程和DestroyJavaVM线程自己,整个虚拟机就退出,此时daemon线程被终止),因此,如果不希望程序退出,只要创建一个非daemon的子线程,让线程不停的sleep即可。
线程的创建
Thread类,有一个start方法,即启动该线程。 启动的线程会执行该类的run方法。注意:因为启动线程时要执行某个过程,因此,通常是需要重新实现run方法的
线程的结束
run模块执行完成主动退出,或者被其他线程强行终止。
通过jstack pid >1.txt
线程状态样例
等待状态样例
上面例子中,IoWaitThread 线程保持等待状态并从 LinkedBlockingQueue 接收消息,如果 LinkedBlockingQueue 一直没有消息,该线程的状态将不会改变。
阻塞状态样例
在上面的例子中,BLOCKED_TEST pool-1-thread-1 线程占用了 <0x0000000780a000b0> 锁,然而 BLOCKED_TEST pool-1-thread-2 和 BLOCKED_TEST pool-1-thread-3 threads 正在等待获取锁。
死锁状态样例
上面的例子中,当线程 A 需要获取线程 B 的锁来继续它的任务,然而线程 B 也需要获取线程 A 的锁来继续它的任务的时候发生的。在 thread dump 中,你能看到 DEADLOCK_TEST-1 线程持有 0x00000007d58f5e48 锁,并且尝试获取 0x00000007d58f5e60 锁。你也能看到 DEADLOCK_TEST-2 线程持有 0x00000007d58f5e60,并且尝试获取 0x00000007d58f5e78,同时 DEADLOCK_TEST-3 线程持有 0x00000007d58f5e78,并且在尝试获取 0x00000007d58f5e48 锁,如你所见,每个线程都在等待获取另外一个线程的锁,这状态将不会被改变直到一个线程丢弃了它的锁。
无限等待的Runnable状态样例
上例中线程的状态是RUNNABLE,但在下面的堆栈日志中发现socketReadThread 线程正在无限等待读取 socket,因此不能单纯通过线程的状态来确定线程是否处于阻塞状态,应该根据详细的堆栈信息进行分析。