Java併發編程實戰(5)- 線程生命週期

在這篇文章中,咱們來聊一下線程的生命週期。java

概述

線程是操做系統中的一個概念,在Java中,它是實現併發程序的主要手段。編程

Java中的線程,本質上就是操做系統中的線程。多線程

操做系統中的線程有「生老病死」,專業說法就是生命週期,雖然不一樣的開發語言對於操做系統的線程作了不一樣的封裝,可是對於線程生命週期來講,基本上是大同小異的。併發

咱們在學習線程的生命週期時,只要能理解生命週期中各個節點的狀態轉換機制就能夠了。socket

操做系統中的線程生命週期

操做系統中的線程有5種狀態,能夠用下面的圖進行描述,該圖也被稱爲五態模型。
tcp

線程的五種狀態描述以下:編程語言

  • 初始狀態。這是指線程已經被建立,可是還不容許分配CPU執行。這個狀態屬於編程語言特有的,也就是說,這裏所謂的被建立,僅僅是在編程語言層面上被建立,在操做系統層面上,線程是尚未被建立的。
  • 可運行狀態。這是指線程能夠分配CPU執行。在這個狀態下,操做系統已經建立了線程,正在等待分配CPU。
  • 運行狀態。這是指CPU有空閒,操做系統將其分配給一個處於可運行狀態的線程,被分配到CPU的線程,就能夠正常執行線程中的邏輯了。
  • 休眠狀態。 這是指處於運行狀態的線程調用了一個阻塞API或者等待某個事件。在休眠狀態下,線程會釋放CPU使用權,處於休眠狀態的線程,是永遠沒有機會得到CPU使用權的。 當等待的事件出現了,線程就會從休眠狀態轉換到可運行狀態,等待CPU從新分配。
  • 終止狀態。這是指線程執行完成或者拋出異常。終止狀態的線程不會切換到其餘任何狀態,這也意味着進行終止狀態的線程的生命週期結束了。

Java中的線程生命週期

Java中的線程生命週期,基於操做系統的線程生命週期進行了定製,它包括六種狀態:工具

  • NEW(初始化狀態)
  • RUNNABLE(可運行/運行狀態)
  • BLOCKED(阻塞狀態)
  • WAITING(無時限等待)
  • TIMED_WAITING(有時限等待)
  • TERMINATED(終止狀態)

Java中的線程生命週期能夠用下圖來描述。
oop

和操做系統線程生命週期相比,Java中的線程生命週期主要有如下2個改動:學習

  • Java線程中對可運行狀態和運行狀態進行了合併。
  • Java線程中的休眠狀態被細化爲:阻塞狀態、無時限等待和有時限等待。

Java線程狀態轉換

Java線程狀態中的阻塞、無時限等待和有時限等待能夠理解爲線程致使休眠狀態的三種緣由,咱們來看一下這些狀態之間是怎麼轉換的。

運行狀態和阻塞狀態之間的轉換

在Java中,只有一種狀況會出現這種狀態轉換:線程等待synchronized隱式鎖。synchronized修飾的方法、代碼塊同一時刻只容許一個線程執行,其餘線程只能等待,在這種狀況下,等待的線程會從運行狀態轉換到阻塞狀態,而當等待的線程得到synchronized鎖後,狀態會從阻塞狀態轉換爲運行狀態。

線程調用阻塞式API時,會切換到阻塞狀態嗎?

在操做系統層面,線程是會切換到休眠狀態的,可是在JVM層面,Java線程的狀態不會切換,也就說Java線程依然是運行狀態。JVM不關心操做系統調度相關的狀態。在JVM看來,等待CPU使用權和等待I/O沒有區別,都是在等待某個資源,因此都屬於可運行/運行狀態。

咱們平時說的Java調用阻塞式API時,線程會被阻塞,咱們指的是操做系統線程狀態,而不是Java線程狀態,這一點須要分清楚。

運行狀態和無時限等待狀態的切換

如下三種狀況會觸發運行狀態和無時限等待狀態的切換。

  • 得到synchronized鎖的線程,調用了無參數的Object.wait()方法。
  • 調用無參數的Thread.join()方法。
  • 調用LockSupport.park()方法。

運行狀態和有時限等待狀態的切換

有時限等待和無時限等待的主要區別,在於觸發條件中添加了超時參數。

如下五種狀況會觸發運行狀態和有時限等待狀態的切換。

  • 調用帶超時參數的Thread.sleep(long millis)方法。
  • 得到synchronized鎖的線程,調用帶超時參數的Object.wait(long timeout)方法。
  • 調用帶超時參數的Thread.join(long millis)方法。
  • 調用帶超時參數的LocakSupport.parkNanos(Object blocker, long deadline)方法。
  • 調用帶超時參數的LockSupport.parkUntil(long deadlinie)方法。

初始化狀態和運行狀態的切換

Java剛建立出來的Thread對象就是初始化狀態,有兩種能夠建立線程的方法:

  • 繼承Thread類
  • 實現Runnable接口

初始化狀態的線程,並不會被操做系統調度,所以不會被執行。在調用線程對象的start()方法後,線程就會從初始化狀態切換到運行狀態。

運行狀態和終止狀態的切換

線程在如下兩種狀況時會自動切換到終止狀態:

  • 正常執行完run()方法
  • run()方法中拋出異常

手動終止線程

咱們有2種方法終止線程:

  • 調用stop()方法
  • 調用interrupt()方法

咱們不推薦使用stop()方法,在JDK中,它已經被標記爲Deprecated。咱們推薦使用interrupt()方法來終止線程。

stop()方法和interrupt()方法的區別:

  • stop()方法會直接殺死線程,不給線程喘息的機會,若是此時線程持有鎖,那麼這個鎖不會被釋放,其餘線程也沒有辦法獲取這個鎖。
  • interrupt()方法只是通知該線程,線程有機會執行一些後續操做,同時也能夠無視這個通知。

被調用了interrupt()方法的線程,有如下2種方式接收通知:

  • 異常,處於有時限等待或者無時限等待狀態的線程, 在被調用interrupt()方法後,線程會返回運行狀態,但同時會拋出InterruptedException。
  • 主動監測,線程能夠調用isInterrupted()方法,來判斷本身是否是被中斷了。

使用jstack查看多線程狀態

在查看了Java線程生命週期中的狀態以及狀態之間的切換後,咱們來使用jstack來查看一下真實運行的線程的狀態。

咱們以一個死鎖的程序爲例,來講明如何使用jstack。

咱們在解釋互斥鎖和死鎖的時候,寫了一些死鎖示例,代碼以下。

public class BankTransferDemo {
	
	public void transfer(BankAccount sourceAccount, BankAccount targetAccount, double amount) {
		synchronized(sourceAccount) {
			synchronized(targetAccount) {
				if (sourceAccount.getBalance() > amount) {
					System.out.println("Start transfer.");
					System.out.println(String.format("Before transfer, source balance:%s, target balance:%s", sourceAccount.getBalance(), targetAccount.getBalance()));
					sourceAccount.setBalance(sourceAccount.getBalance() - amount);
					targetAccount.setBalance(targetAccount.getBalance() + amount);
					System.out.println(String.format("After transfer, source balance:%s, target balance:%s", sourceAccount.getBalance(), targetAccount.getBalance()));
				}
			}
		}
	}
}
public static void main(String[] args) throws InterruptedException {
		BankAccount sourceAccount = new BankAccount();
		sourceAccount.setId(1);
		sourceAccount.setBalance(50000);
		
		BankAccount targetAccount = new BankAccount();
		targetAccount.setId(2);
		targetAccount.setBalance(20000);
		
		BankTransferDemo obj = new BankTransferDemo();
		
		Thread t1 = new Thread(() ->{
			for (int i = 0; i < 10000; i++) {
				obj.transfer(sourceAccount, targetAccount, 1);
			}
		});
		
		Thread t2 = new Thread(() ->{
			for (int i = 0; i < 10000; i++) {
				obj.transfer(targetAccount, sourceAccount, 1);
			}
		});
		
		t1.start();
		t2.start();
		
		t1.join();
		t2.join();
		
		System.out.println("Finished.");
	}

上述代碼在運行過程當中,由於資源爭搶的緣由,最後會進入死鎖狀態,下面咱們來看一下如何使用jstack來獲取具體信息。

(base) ➜  ~ jstack -l 63044

請注意上述的63044是運行的pid,運行程序屢次產生的pid是不同的。

jstack的返回結果以下。

2021-01-15 19:56:28
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.251-b08 mixed mode):

"RMI TCP Accept-0" #14 daemon prio=9 os_prio=31 tid=0x00007fb1d80b6000 nid=0x5803 runnable [0x00007000059d8000]
   java.lang.Thread.State: RUNNABLE
	at java.net.PlainSocketImpl.socketAccept(Native Method)
	at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
	at java.net.ServerSocket.implAccept(ServerSocket.java:545)
	at java.net.ServerSocket.accept(ServerSocket.java:513)
	at sun.management.jmxremote.LocalRMIServerSocketFactory$1.accept(LocalRMIServerSocketFactory.java:52)
	at sun.rmi.transport.tcp.TCPTransport$AcceptLoop.executeAcceptLoop(TCPTransport.java:405)
	at sun.rmi.transport.tcp.TCPTransport$AcceptLoop.run(TCPTransport.java:377)
	at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
	- None

"Attach Listener" #12 daemon prio=9 os_prio=31 tid=0x00007fb1db03d800 nid=0x3617 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"Thread-1" #11 prio=5 os_prio=31 tid=0x00007fb1db04e800 nid=0xa603 waiting for monitor entry [0x00007000057d2000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at com.concurrency.demo.BankTransferDemo.transfer(BankTransferDemo.java:8)
	- waiting to lock <0x000000076ab76ef0> (a com.concurrency.demo.BankAccount)
	- locked <0x000000076ab76f10> (a com.concurrency.demo.BankAccount)
	at com.concurrency.demo.BankTransferDemo.lambda$1(BankTransferDemo.java:38)
	at com.concurrency.demo.BankTransferDemo$$Lambda$2/1044036744.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
	- None

"Thread-0" #10 prio=5 os_prio=31 tid=0x00007fb1d896e000 nid=0xa703 waiting for monitor entry [0x00007000056cf000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at com.concurrency.demo.BankTransferDemo.transfer(BankTransferDemo.java:8)
	- waiting to lock <0x000000076ab76f10> (a com.concurrency.demo.BankAccount)
	- locked <0x000000076ab76ef0> (a com.concurrency.demo.BankAccount)
	at com.concurrency.demo.BankTransferDemo.lambda$0(BankTransferDemo.java:32)
	at com.concurrency.demo.BankTransferDemo$$Lambda$1/135721597.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
	- None

"Service Thread" #9 daemon prio=9 os_prio=31 tid=0x00007fb1de809000 nid=0x5503 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"C1 CompilerThread3" #8 daemon prio=9 os_prio=31 tid=0x00007fb1df80a800 nid=0x3b03 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"C2 CompilerThread2" #7 daemon prio=9 os_prio=31 tid=0x00007fb1df80a000 nid=0x3a03 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"C2 CompilerThread1" #6 daemon prio=9 os_prio=31 tid=0x00007fb1df809000 nid=0x3e03 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"C2 CompilerThread0" #5 daemon prio=9 os_prio=31 tid=0x00007fb1df008800 nid=0x3803 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"Signal Dispatcher" #4 daemon prio=9 os_prio=31 tid=0x00007fb1de808800 nid=0x4103 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"Finalizer" #3 daemon prio=8 os_prio=31 tid=0x00007fb1d8810800 nid=0x3203 in Object.wait() [0x0000700004db1000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x000000076ab08ee0> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
	- locked <0x000000076ab08ee0> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
	at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

   Locked ownable synchronizers:
	- None

"Reference Handler" #2 daemon prio=10 os_prio=31 tid=0x00007fb1d900b000 nid=0x3103 in Object.wait() [0x0000700004cae000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x000000076ab06c00> (a java.lang.ref.Reference$Lock)
	at java.lang.Object.wait(Object.java:502)
	at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
	- locked <0x000000076ab06c00> (a java.lang.ref.Reference$Lock)
	at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

   Locked ownable synchronizers:
	- None

"main" #1 prio=5 os_prio=31 tid=0x00007fb1db809000 nid=0x1003 in Object.wait() [0x000070000408a000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x000000076ab770c0> (a java.lang.Thread)
	at java.lang.Thread.join(Thread.java:1252)
	- locked <0x000000076ab770c0> (a java.lang.Thread)
	at java.lang.Thread.join(Thread.java:1326)
	at com.concurrency.demo.BankTransferDemo.main(BankTransferDemo.java:45)

   Locked ownable synchronizers:
	- None

"VM Thread" os_prio=31 tid=0x00007fb1db821000 nid=0x4c03 runnable

"GC task thread#0 (ParallelGC)" os_prio=31 tid=0x00007fb1db809800 nid=0x1f07 runnable

"GC task thread#1 (ParallelGC)" os_prio=31 tid=0x00007fb1d8008800 nid=0x1b03 runnable

"GC task thread#2 (ParallelGC)" os_prio=31 tid=0x00007fb1db009000 nid=0x1d03 runnable

"GC task thread#3 (ParallelGC)" os_prio=31 tid=0x00007fb1db009800 nid=0x2a03 runnable

"GC task thread#4 (ParallelGC)" os_prio=31 tid=0x00007fb1db00a000 nid=0x2c03 runnable

"GC task thread#5 (ParallelGC)" os_prio=31 tid=0x00007fb1db00a800 nid=0x2d03 runnable

"GC task thread#6 (ParallelGC)" os_prio=31 tid=0x00007fb1db80a000 nid=0x5203 runnable

"GC task thread#7 (ParallelGC)" os_prio=31 tid=0x00007fb1db00b800 nid=0x5003 runnable

"GC task thread#8 (ParallelGC)" os_prio=31 tid=0x00007fb1db00c000 nid=0x4f03 runnable

"GC task thread#9 (ParallelGC)" os_prio=31 tid=0x00007fb1d900a800 nid=0x4d03 runnable

"VM Periodic Task Thread" os_prio=31 tid=0x00007fb1d8028800 nid=0xa803 waiting on condition

JNI global references: 333


Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00007fb1db8270a8 (object 0x000000076ab76ef0, a com.concurrency.demo.BankAccount),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007fb1db827158 (object 0x000000076ab76f10, a com.concurrency.demo.BankAccount),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
	at com.concurrency.demo.BankTransferDemo.transfer(BankTransferDemo.java:8)
	- waiting to lock <0x000000076ab76ef0> (a com.concurrency.demo.BankAccount)
	- locked <0x000000076ab76f10> (a com.concurrency.demo.BankAccount)
	at com.concurrency.demo.BankTransferDemo.lambda$1(BankTransferDemo.java:38)
	at com.concurrency.demo.BankTransferDemo$$Lambda$2/1044036744.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)
"Thread-0":
	at com.concurrency.demo.BankTransferDemo.transfer(BankTransferDemo.java:8)
	- waiting to lock <0x000000076ab76f10> (a com.concurrency.demo.BankAccount)
	- locked <0x000000076ab76ef0> (a com.concurrency.demo.BankAccount)
	at com.concurrency.demo.BankTransferDemo.lambda$0(BankTransferDemo.java:32)
	at com.concurrency.demo.BankTransferDemo$$Lambda$1/135721597.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

咱們從中能夠看到線程的狀態有RUNNABLE,WAITING,BLOCKED,例如:

"Thread-0" #10 prio=5 os_prio=31 tid=0x00007fb1d896e000 nid=0xa703 waiting for monitor entry [0x00007000056cf000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at com.concurrency.demo.BankTransferDemo.transfer(BankTransferDemo.java:8)
	- waiting to lock <0x000000076ab76f10> (a com.concurrency.demo.BankAccount)
	- locked <0x000000076ab76ef0> (a com.concurrency.demo.BankAccount)
	at com.concurrency.demo.BankTransferDemo.lambda$0(BankTransferDemo.java:32)
	at com.concurrency.demo.BankTransferDemo$$Lambda$1/135721597.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
	- None

下面是死鎖的相關信息:

Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00007fb1db8270a8 (object 0x000000076ab76ef0, a com.concurrency.demo.BankAccount),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007fb1db827158 (object 0x000000076ab76f10, a com.concurrency.demo.BankAccount),
  which is held by "Thread-1"

從上面的描述中,咱們能夠清楚的看到2個線程在互相等待對方持有的鎖對象。

jstack是一個很是實用的工具,我會在後面找機會詳細的說明如何使用它和其餘相關工具。

相關文章
相關標籤/搜索