技術問答集錦(二)

猿碼道發表於2017-12-30

4 如何通過JDK命令,分析排查死鎖、死迴圈?

top 命令:顯示當前的活動程式,預設它是按消耗 CPU 的厲害程度進行排序,每5秒鐘重新整理一次列表,你也可以選擇不同的排序方式,例如 m 是按記憶體佔用方式進行排序的快捷鍵。

命令:top -Hp pid

top -Hp pid

可以實時的跟蹤並獲取指定程式中最耗cpu的執行緒。再用 jstack方法提取到對應的執行緒堆疊資訊

jps 命令:

jps用來檢視JVM裡面所有程式的具體狀態, 包括程式ID,程式啟動的路徑等等。

jstack 命令:

  1. 檢視java程式崩潰生成core檔案,獲得core檔案的java stack和native stack的資訊;
  2. 檢視正在執行的java程式的java stack和native stack的資訊:a) 檢視執行的java程式呈現hung的狀態;b) 跟蹤Java的呼叫棧,剖析程式。

執行緒的狀態分析:

Runnable:該狀態 表示執行緒具備所有執行條件,在執行佇列中準備作業系統的排程,或者正在執行

Wait on condition:該狀態出現線上程等待某個條件的發生。具體是什麼原因,可以結合 stacktrace來分析。最常見的情況是執行緒在等待網路的讀寫;另外一種出現 Wait on condition的常見情況是 該執行緒在 sleep,等待 sleep的時間到了時候,將被喚醒

Waiting for monitor entry 和 in Object.wait():在多執行緒的 JAVA程式中,實現執行緒之間的同步,就要說說 Monitor。 Monitor是Java中用以實現執行緒之間的互斥與協作的主要手段,它可以看成是物件或者 Class的鎖。每一個物件都有,也僅有一個 monitor

jinfo 命令:

jinfo可觀察執行中的java程式的執行環境引數:引數包括Java System屬性和JVM命令列引數;也可從core檔案裡面知道崩潰的Java應用程式的配置資訊。

jstat 命令:

jstat利用了JVM內建的指令 對Java應用程式的資源和效能進行實時的命令列的監控,包括了對Heap size和垃圾回收狀況的監控等等。

jmap 命令:

觀察執行中的jvm實體記憶體的佔用情況,包括Heap size, Perm size等等。

4.1 Java死迴圈分析

  • 檢視程式ID:top 或者 jps

  • 按CPU使用率展示當前JAVA程式的所有執行緒:top -Hp 3230

    top -Hp 3230

其實這個地方按CPU的使用率來判定還不太好理解,以執行時間來判定可能更能說明問題些。

  • 將執行時間最長的 本地執行緒ID(3244)轉成16進製為0xcac

  • 生成執行緒堆疊日誌檔案 jstack -l 3230 > jstack.log

  • 開啟堆疊日誌 搜尋“0xcac”

    堆疊日誌搜尋“0xcac”

  • 很容易的就找到了無限迴圈的呼叫執行緒堆疊。

4.2 Java死鎖分析

在多執行緒程式的編寫中,如果不適當的運用同步機制,則有可能造成程式的死鎖,經常表現為程式的停頓,或者不再響應使用者的請求。 比如在下面這個示例中,是個較為典型的死鎖情況:

較為典型的死鎖情況

5 Java中j.u.c包原理實現及CAS演算法?

5.1 Java的多執行緒同步機制

在現代的多處理器系統中,提高程式的並行執行能力是 有效利用 CPU 資源的關鍵。為了有效協調多執行緒間的併發訪問,必須採用適當的同步機制來協調競爭。當前常用的多執行緒同步機制可以分為下面三種型別:

volatile 變數:輕量級多執行緒同步機制,不會引起上下文切換和執行緒排程。僅提供記憶體可見性保證,不提供原子性。

CAS 原子指令:輕量級多執行緒同步機制,不會引起上下文切換和執行緒排程。它同時提供記憶體可見性和原子化更新保證。

內部鎖和顯式鎖:重量級多執行緒同步機制,可能會引起上下文切換和執行緒排程,它同時提供記憶體可見性和原子性。

在這裡,CAS 指的是現代 CPU 廣泛支援的一種對記憶體中的共享資料進行操作的一種特殊指令。這個指令會對記憶體中的共享資料做原子的讀寫操作。簡單介紹一下這個指令的操作過程:首先,CPU 會將記憶體中將要被更改的資料與期望的值做比較。然後,當這兩個值相等時,CPU 才會將記憶體中的數值替換為新的值。否則便不做操作。最後,CPU 會將舊的數值返回。這一系列的操作是原子的。它們雖然看似複雜,但卻是 Java 5 併發機制優於原有鎖機制的根本。簡單來說,CAS 的含義是“我認為原有的值應該是什麼,如果是,則將原有的值更新為新值,否則不做修改,並告訴我原來的值是多少”。

在輕度到中度的爭用情況下,非阻塞演算法的效能會超越阻塞演算法,因為 CAS 的多數時間都在第一次嘗試時就成功,而發生爭用時的開銷也不涉及執行緒掛起和上下文切換,只多了幾個迴圈迭代。沒有爭用的 CAS 要比沒有爭用的鎖便宜得多(這句話肯定是真的,因為沒有爭用的鎖涉及 CAS 加上額外的處理),而爭用的 CAS 比爭用的鎖獲取涉及更短的延遲。

5.2 Java中j.u.c包原理實現

Java的CAS會使用現代處理器上提供的高效機器級別原子指令,這些原子指令以原子方式對記憶體執行讀-改-寫操作,這是在多處理器中實現同步的關鍵(從本質上來說,能夠支援原子性讀-改-寫指令的計算機器,是順序計算圖靈機的非同步等價機器,因此 任何現代的多處理器都會去支援某種能對記憶體執行原子性讀-改-寫操作的原子指令)。同時,volatile變數的讀/寫和CAS可以實現執行緒之間的通訊。把這些特性整合在一起,就形成了整個concurrent包得以實現的基石。如果我們仔細分析concurrent包的原始碼實現,會發現一個通用化的實現模式:

  1. 首先,宣告共享變數為volatile;
  2. 然後,使用CAS的原子條件更新來實現執行緒之間的同步;
  3. 同時,配合以volatile的讀/寫和CAS所具有的volatile讀和寫的記憶體語義來實現執行緒之間的通訊。

AQS,非阻塞資料結構和原子變數類(java.util.concurrent.atomic包中的類),這些concurrent包中的基礎類都是使用這種模式來實現的,而concurrent包中的高層類又是依賴於這些基礎類來實現的。從整體來看,concurrent包的實現示意圖如下:

concurrent包實現示意圖

6 有鎖與無鎖的本質區別?

有鎖、無鎖的本質都是在解決併發情況下競態資源的執行緒安全問題。無鎖只是把“排他性”進一步的弱化,以提高併發量,最大限度的使用CPU

無鎖只會在CPU控制權切換的等待,而有鎖會在“鎖釋放”、“CPU控制權切換”兩種情況下的等待。

最終無鎖情況下,CPU使用率上不去,吞吐量下降,就受系統資源限制了。即使執行緒量增加,無非增加的是執行緒CPU控制權的切換成本,統一受CPU的控制排程,出現的也只能是等待了。

7 volatile關鍵字是否是執行緒安全的?

volatile關鍵字僅保證兩點:

  1. volatile變數執行緒之間可見性;
  2. 禁止針對volatile變數操作指令重排序;

但最重要一點沒有保證,關係到執行緒安全,就是Volatile變數操作指令的不保證原子性問題

什麼是原子操作:

多個執行緒執行一個操作時,其中任何一個執行緒要麼完全執行完此操作的步驟,要麼就沒有執行此操作的任何步驟,那麼認為這個操作才是原子的。

關於指令重排序的問題,在單執行緒中,重排序的前後只要不影響JVM執行結果,JVM可以執行指令重排序

指令重排序的意義在於:

JVM能夠根據處理器特性(CPU多級快取系統、多核處理器等)適當的重新排序機器指令,使機器指令更符合CPU的執行特點,最大限度的發揮機器效能。

但是 在指令重排序在多執行緒的情況下,重排序的前後結果,可能不會一致了。這裡導致結果不一致主要是執行緒安全的問題,即使指令不重排序,也會存線上程安全問題。這也就是原子性問題、或者沒有加鎖的問題

相關文章