JVM線程屬於用戶態還是內核態 當進程運行在ring3級別時為用戶態,ring0級別時為內核態 有些操作需要有內核許可權才能進行,那麼有三種由用戶態切換到內核態的情況: 系統調用:操作系統封裝內核指令,統一管理硬體資源,然後向用戶程式提供系統服務,用戶程式進行系統調用,操作系統進行檢查確保全全然後再進 ...
JVM線程屬於用戶態還是內核態
當進程運行在ring3級別時為用戶態,ring0級別時為內核態
有些操作需要有內核許可權才能進行,那麼有三種由用戶態切換到內核態的情況:
- 系統調用:操作系統封裝內核指令,統一管理硬體資源,然後向用戶程式提供系統服務,用戶程式進行系統調用,操作系統進行檢查確保全全然後再進行相應的資源訪問操作。比如malloc(),print()調用write()系統輸出字元串
- 異常事件:當cpu正在運行用戶態程式,發生不可預知的異常事件,就會轉用戶態,比如缺頁中斷。
- 外圍設備的中斷:當外圍設備完成請求就會向CPU發出中斷信號,此時cpu暫停下一條要執行的指令,去執行中斷信號所對應的程式
相同點和不同點:都是中斷,但是系統調用是主動,其他都是被動
用戶態和內核態線程的映射關係
一對一(內核線程實現)
程式使用輕量級進程和內核線程產生映射
缺點:輕量級進程的數量有限制,執行效率低
多對一(用戶線程實現)
優點:用戶線程數量幾乎無限制,執行效率高
缺點:一個用戶線程阻塞,其他線程也會阻塞
多對多
UT 用戶態線程 LWP 輕量級線程 KLT 內核態線程
優點:
- 一個用戶線程的阻塞不會導致所有線程的阻塞,因為此時還有別的內核線程被調度來執行。
- 多對多模型對用戶線程的數量沒有限制。
- 在多處理器的操作系統中,多對多模型的線程也能得到一定的性能提升,但提升的幅度不如一對一模型的高。在現在流行的操作系統中,大都採用多對多的模型。
用戶態線程:切換代價小,高併發但是容易阻塞
內核態線程:處理能力高,切換代價大
Java線程的實現
虛擬機規範中並沒有限定java線程需要使用哪種線程模型,要根據不同的平臺來說,但是無論使用哪種線程模型,java程式的編碼和運行都是沒有差異的
Java線程調度
線程調度有兩種:協同調度和搶占調度
協同:自己分配時間,自己切換
搶占:系統分配時間,系統決定線程的切換
java線程採用搶占調度
java線程的6種狀態
新建———-運行———無限期等待——–限期等待———阻塞——結束
操作系統線程的幾種狀態
新建———就緒————等待————運行———–結束
java和操作系統的線程對應關係
1.2之前(綠色線程 1:N),程式員為jvm開發了一個線程調度內核,映射到操作系統層面就是用戶態線程;
1.2(1:1)之後,jvmU型安澤了操作系統原生線程模型,映射到操作系統層面就是內核態線程,通過系統調用,將程式的線程交給了操作系統內核進行調度。
通過創建過程來理解
Java的Thread對象:僅僅是一個Java對象
JVM的JavaThread對象:連接著java的Thread對象與OS對象
JVM的OSThread對象:一個工具類,對OS線程API進行了功能性封裝
流程圖:
JVM_StartThread核心做了兩件事情:
1.創建JavaThread對象
(1) 設置jvm執行run方法的跳板
(2) 調用os::create_thread創建OSThread對象及操作系統線程完成三者的關聯
os::create_thread做了一下這些:
創建OSThread對象,將JavaThread對象與OSThread對象進行關聯
線程庫
為開發人員提供創建和管理線程的一套API
三個主要的線程庫:
1)POSIX Pthreads:可以作為用戶或內核庫提供,作為 POSIX 標準的擴展
2)Win32 線程:用於 Window 操作系統的內核級線程庫
3)Java 線程:Java 線程 API 通常採用宿主系統的線程庫來實現,也就是說在 Win 系統上,Java 線程 API 通常採用 Win API 來實現,在 UNIX 類系統上,採用 Pthread 來實現。
操作系統對於鎖的實現
在硬體層面,CPU提供了原子操作、關中斷(可解決單核情況下兩個線程同時獲得鎖)、鎖記憶體匯流排的機制(解決多核情況下兩個線程同時獲得鎖);OS基於這幾個CPU硬體機制,就能夠實現鎖;再基於鎖,就能夠實現各種各樣的同步機制(信號量、消息、Barrier等等等等)
synchronized的底層實現
是通過對象內部的一個監視器鎖(monitor)實現的,監視器鎖有時通過操作系統的互斥鎖來實現的,而且現在主流的java虛擬機實現中,java的線程是映射到操作系統原生的內核線程中的,那麼線程的阻塞或喚醒,就涉及到用戶態和內核態的轉換中,所以是重量級鎖。
是一種塊結構的同步語法,經過javac反編譯之後,會在同步塊的前後分別生成monitorenter和monitorexit兩個位元組碼指令,這兩個指令都需要一個reference類型的參數來指明加鎖解鎖的對象,如果synchronized明確了對象參數。就以這個對象的引用作為reference,如果沒有指定,那就根據修飾的方法類型,來決定是區代碼所在的對象實例還是相應的class對象。
反射的性能優化
兩個地方導致性能差:getMethod和invoke,反射是一個解釋操作,臨時告訴jvm應該做什麼
優化思路1:緩存Method,不重覆調用getMethod
優化思路2:藉助ASM框架,使用reflectAsm,讓invoke變成直接調用
借反射的getDeclaredMethods獲取目標類的所有方法,然後動態生成一個繼承於MethodAccess 的子類SimpleBeanMethodAccess,動態生成一個Class文件並load到JVM中。
SimpleBeanMethodAccess中所有方法名建立index索引,index跟方法名是映射的,根據方法名獲得index,SimpleBeanMethodAccess內部建立的switch直接分發執行相應的代碼,這樣methodAccess.invoke的時候,實際上是直接調用。
hashmap在jdk1.7的死迴圈
多線程頭插法造成的,線程2已經完成擴容散列,鏈表變成了倒序,線程1再進行擴容,將倒序鏈表變成了一個正序鏈表,從而形成環形鏈表,在使用get方法時造成死迴圈。
常用集合
ArrayList
首先有三種構造方法(有參,無參,指定集合參數組成的列表)
主要就是三個方法:
1.ensureCapacityInternal得到最小擴容量,併進行擴容
2.ensureExplicitCapacity//判斷是否需要擴容,如果最小擴容量大於數組現在的長度就調用grow方法
3.grow 進行位運算,擴容1.5倍,併進行判斷是否超出數組的最大容量。
始化數據量為0,add時變為10,當 要 add 進第 1 個元素時,minCapacity 為 1,在 ensureCapacityInternal的Math.max()方法比較後,minCapacity 為 10 ,
最好在 add 大量元素之前用 ensureCapacity 方法(因為是public修飾),以減少增量重新分配的次數
Set Map