一、補充知識
補碼: 正數的補碼是其本身,負數的補碼是反碼加1
例如:-6
原碼:10000110 反碼:11111001 補碼:11111010
爲什麼要有補碼的存在?
無歧義的表示零:0; 0既不屬於正數,也不屬於負數
用正數表示0: 源碼: 00000000 反碼: 01111111 補碼: 00000000 用負數表示0: 源碼: 10000000 反碼: 11111111 補碼: 00000000
方便計算機計算,補碼相加符號位可以直接參與運算
二、JVM運行機制
1. JVM啓動流程
說明:
- 裝在配置:根據當前路徑和系統版本尋找jvm.cfg
- JVM.dll爲JVM主要實現
- JNIEnv爲JVM接口,findClass等操作通過它實現
2. JVM基本結構
運行時數據區:
方法區;
- 保存裝載的類信息
- -類型的常量池
- 字段,方法信息
- 方法字節碼
- 通常和永久區(Perm)關聯在一起
PS: JDK6時,String等常量信息置於方法區,JDK7時,已經移動到了堆,JDK8移除永久代,增加了元空間MetaSpace
- 本地方法棧;
- 堆
堆是線程共享的,所有通過new關鍵字生成的對象,都在堆中。java GC主要針對的區域就是堆,GC算法跟堆算法密切相關,例如分代收集算法要求堆,必須是分代存放數據的。 - 棧
線程私有
- 棧由一系列幀組成(因此Java棧也叫做幀棧)
- 幀保存一個方法的局部變量、操作數棧、常量池指針
- 每一次方法調用創建一個幀,並壓棧
棧上分配:
- 小對象(一般幾十個bytes),在沒有逃逸的情況下,可直接分配在棧上
- 直接分配棧上,可以自動回收,減輕GC壓力(一個函數調用結束,棧幀自動回收)
- 大對象或者逃逸對象無法棧上分配
PC寄存器
- 每個線程擁有一個PC寄存器
- 在線程創建時 創建
- 指向下一條指令的地址
- 執行本地方法時,PC的值爲undefined
3. 內存模型
- 每一個線程有一個工作內存和主存獨立
- 工作內存存放主存中變量的值的拷貝
當數據從主內存複製到工作存儲時,必須出現兩個動作
- 第一:由主內存執行的讀(read)操作;
第二,由工作內存執行的相應的load操作;當數據從工作內存拷貝到主內存時,也出現兩個操作:
- 第一個,由工作內存執行的存儲(store)操作;
- 第二個,由主內存執行的相應的寫(write)操作。每一個操作都是原子的,即執行期間不會被中斷,對於普通變量,一個線程中更新的值,不能馬上反應在其他變量中,如果需要在其他線程中立即可見,需要使用 volatile 關鍵字。
如下圖所示:
可見性
- 一個線程修改了變量,其他線程可以立即知道
- 保證可見性的方法
- volatile
- synchronized (unlock之前,寫變量值回主存)
- final(一旦初始化完成,其他線程就可見)
有序性
- 在本線程內,操作都是有序的
- 在線程外觀察,操作都是無序的。(指令重排 或 主內存同步延時)
指令重排
- 線程內串行語義
- 寫後讀 a = 1;b = a; 寫一個變量之後,再讀這個位置。
- 寫後寫 a = 1;a = 2; 寫一個變量之後,再寫這個變量。
- 讀後寫 a = b;b = 1; 讀一個變量之後,再寫這個變量。
- 以上語句不可重排
- 編譯器不考慮多線程間的語義
- 可重排: a=1;b=2;
- 反例:
- 指令重排的基本原則
- 程序順序原則:一個線程內保證語義的串行性
- volatile規則:volatile變量的寫,先發生於讀
- 鎖規則:解鎖(unlock)必然發生在隨後的加鎖(lock)前
- 傳遞性:A先於B,B先於C 那麼A必然先於C
- 線程的start方法先於它的每一個動作
- 線程的所有操作先於線程的終結(Thread.join())
- 線程的中斷(interrupt())先於被中斷線程的代碼
- 對象的構造函數執行結束先於finalize()方法
- 線程內串行語義
4.字節碼運行的兩種方式 - 編譯和解釋運行
- 解釋運行
- 解釋執行以解釋方式運行字節碼
- 解釋執行的意思是:讀一句執行一句
- 編譯運行(JIT– just in time)
- 將字節碼編譯成機器碼
- 直接執行機器碼
- 運行時編譯
- 編譯後性能有數量級的提升
- java代碼->字節碼->機器碼 4.
常用JVM配置參數
Trace跟蹤參數
打印GC的簡要信息
-verbose:gc
和-XX:+printGC
:
輸出如下:[GC 4790K->374K(15872K), 0.0001606 secs]
[GC 4790K->374K(15872K), 0.0001474 secs]
[GC 4790K->374K(15872K), 0.0001563 secs]
[GC 4790K->374K(15872K), 0.0001682 secs]打印GC詳細信息 :
-XX:+PrintGCDetails
打印CG發生的時間戳:-XX:+PrintGCTimeStamps
例:
[GC[DefNew: 4416K->0K(4928K), 0.0001897 secs] 4790K->374K(15872K), 0.0002232 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
PrintGCDetails的輸出
def new generation total 13824K, used 11223K [0x27e80000, 0x28d80000, 0x28d80000) eden space 12288K, 91% used [0x27e80000, 0x28975f20, 0x28a80000) from space 1536K, 0% used [0x28a80000, 0x28a80000, 0x28c00000) to space 1536K, 0% used [0x28c00000, 0x28c00000, 0x28d80000) tenured generation total 5120K, used 0K [0x28d80000, 0x29280000, 0x34680000) the space 5120K, 0% used [0x28d80000, 0x28d80000, 0x28d80200, 0x29280000) compacting perm gen total 12288K, used 142K [0x34680000, 0x35280000, 0x38680000) the space 12288K, 1% used [0x34680000, 0x346a3a90, 0x346a3c00, 0x35280000) ro space 10240K, 44% used [0x38680000, 0x38af73f0, 0x38af7400, 0x39080000) rw space 12288K, 52% used [0x39080000, 0x396cdd28, 0x396cde00, 0x39c80000)
-Xloggc:log/gc.log
:- 指定GC log的位置,以文件輸出
- 幫助開發人員分析問題
-XX:+PrintHeapAtGC
:- 每次一次GC後,都打印堆信息
-XX:+TraceClassLoading
:
監控類的加載[Loaded java.lang.Object from shared objects file] [Loaded java.io.Serializable from shared objects file] [Loaded java.lang.Comparable from shared objects file] [Loaded java.lang.CharSequence from shared objects file] [Loaded java.lang.String from shared objects file] [Loaded java.lang.reflect.GenericDeclaration from shared objects file] [Loaded java.lang.reflect.Type from shared objects file]
-XX:+PrintClassHistogram
:- 按下Ctrl+Break後,打印類的信息:
- 分別顯示:序號、實例數量、總大小、類型
num | instances | bytes | class name |
---|---|---|---|
1: | 890617 | 470266000 | [B |
2: | 890643 | 21375432 | java.util.HashMap$Node |
3: | 890608 | 14249728 | java.lang.Long |
4: | 13 | 8389712 | [Ljava.util.HashMap$Node; |
5: | 2062 | 371680 | [C |
6: | 463 | 41904 | java.lang.Class |
堆的分配參數
-Xmx
–Xms
: 指定最大堆和最小堆例: -Xmx20m -Xms5m :
/**代碼*/ System.out.print("Xmx="); System.out.println(Runtime.getRuntime().maxMemory()/1024.0/1024+"M"); System.out.print("free mem="); System.out.println(Runtime.getRuntime().freeMemory()/1024.0/1024+"M"); System.out.print("total mem="); System.out.println(Runtime.getRuntime().totalMemory()/1024.0/1024+"M"); /**輸出結果*/ Xmx=19.375M free mem=4.342750549316406M total mem=4.875M
Java會儘可能維持在最小堆,所以free mem和total mem的值會隨着內存使用的變化而變化
-Xmn
:設置新生代大小-XX:NewRatio
:
- 新生代(eden+2*s)和老年代(不包含永久區)的比值
- 4 表示 新生代:老年代=1:4,即年輕代佔堆的1/5
-XX:SurvivorRatio
- 設置兩個Survivor區和eden的比
- 8表示 兩個Survivor :eden=2:8,即一個Survivor佔年輕代的1/10
-XX:+HeapDumpOnOutOfMemoryError
- OOM時導出堆到文件
-XX:+HeapDumpPath
- 導出OOM的路徑
-XX:OnOutOfMemoryError
- 在OOM時,執行一個腳本”-XX:OnOutOfMemoryError=D:/tools/jdk1.7_40/bin/printstack.bat %p”
- 當程序OOM時,在D:/a.txt中將會生成線程的dump
- 可以在OOM時,發送郵件,甚至是重啓程序
-XX:PermSize
-XX:MaxPermSize
- 設置永久區的初始空間和最大空間
- 他們表示,一個系統可以容納多少個類型
- 使用CGLIB等庫的時候,可能會產生大量的類,這些類,有可能撐爆永久區導致OOM
根據實際事情調整新生代和倖存代的大小
官方推薦新生代佔堆的3/8
倖存代佔新生代的1/10
在OOM時,記得Dump出堆,確保可以排查現場問題
棧的分配參數
-Xss
- 通常只有幾百K
- 決定了函數調用的深度
- 每個線程都有獨立的棧空間
- 局部變量、參數 分配在棧上
GC 算法與種類
GC的對象是堆空間和永久區
GC的算法
引用計數法
- 老牌垃圾回收算法
- 通過引用計算來回收垃圾
引用計數器
的實現很簡單,對於一個對象A,只要有任何一個對象引用了A,則A的引用計數器就加1,當引用失效時,引用計數器就減1。只要對象A的引用計數器的值爲0,則對象A就不可能再被使用。- 引用計數法的問題:
- 引用和去引用伴隨加法和減法,影響性能
- 很難處理循環引用
- 使用者:
- COM
- ActionScript3
- Python
標記清除
標記-清除
算法是現代垃圾回收算法的思想基礎。標記-清除算法將垃圾回收分爲兩個階段:標記階段和清除階段。一種可行的實現是,在標記階段,首先通過根節點,標記所有從根節點開始的可達對象。因此,未被標記的對象就是未被引用的垃圾對象。然後,在清除階段,清除所有未被標記的對象
- 標記壓縮
標記-壓縮算法適合用於存活對象較多的場合,如老年代。它在標記-清除算法的基礎上做了一些優化。和標記-清除算法一樣,標記-壓縮算法也首先需要從根節點開始,對所有可達對象做一次標記。但之後,它並不簡單的清理未標記的對象,而是將所有的存活對象壓縮到內存的一端。之後,清理邊界外所有的空間。
- 複製算法
- 與標記-清除算法相比,複製算法是一種相對高效的回收方法
- 不適用於存活對象較多的場合 如老年代
- 將原有的內存空間分爲兩塊,每次只使用其中一塊,在垃圾回收時,將正在使用的內存中的存活對象複製到未使用的內存塊中,之後,清除正在使用的內存塊中的所有對象,交換兩個內存的角色,完成垃圾回收
複製算法的最大問題是:空間浪費 整合標記清理思想
- 複製算法優化【分代收集思想】
依據對象的存活週期進行分類,短命對象歸爲新生代,長命對象歸爲老年代。
根據不同代的特點,選取合適的收集算法:
- 少量對象存活,適合複製算法
- 大量對象存活,適合標記清理或者標記壓縮
可觸及性
- 可觸及的
- 從根節點可以觸及到這個對象
- 可復活的
- 一旦所有引用被釋放,就是可復活狀態
- 因爲在finalize()中可能復活該對象
不可觸及的
- 在finalize()後,可能會進入不可觸及狀態
- 不可觸及的對象不可能復活
- 可以回收
經驗
- 避免使用finalize(),操作不慎可能導致錯誤。
- 優先級低,何時被調用, 不確定
- 何時發生GC不確定
- 可以使用try-catch-finally來替代它
- 根
- 棧中引用的對象
- 方法區中靜態成員或者常量引用的對象(全局對象)
- JNI方法棧中引用對象
Stop-The-World
- Java中一種全局暫停的現象
- 全局停頓,所有Java代碼停止,native代碼可以執行,但不能和JVM交互
- 多半由於GC引起:
- Dump線程
- 死鎖檢查
- 堆Dump
- GC時爲什麼會有全局停頓?
類比在聚會時打掃房間,聚會時很亂,又有新的垃圾產生,房間永遠打掃不乾淨,只有讓大家停止活動了,才能將房間打掃乾淨。 - 危害
- 長時間服務停止,沒有響應
- 遇到HA系統,可能引起主備切換,嚴重危害生產環境。
GC參數
串行收集器
最古老,最穩定
效率高
可能會產生較長的停頓
-XX:+UseSerialGC
新生代、老年代使用串行回收
新生代複製算法
老年代標記-壓縮
並行收集器
ParNew
-XX:+UseParNewGC
新生代並行
老年代串行
Serial收集器新生代的並行版本
複製算法
多線程,需要多核支持
-XX:ParallelGCThreads 限制線程數量
Parallel收集器
類似ParNew
新生代複製算法
老年代 標記-壓縮
更加關注吞吐量
-XX:+UseParallelGC
使用Parallel收集器+ 老年代串行
-XX:+UseParallelOldGC
使用Parallel收集器+ 並行老年代
-XX:MaxGCPauseMills
最大停頓時間,單位毫秒
GC盡力保證回收時間不超過設定值
-XX:GCTimeRatio
0-100的取值範圍
垃圾收集時間佔總時間的比
默認99,即最大允許1%時間做GC
這兩個參數是矛盾的。因爲停頓時間和吞吐量不可能同時調優
CMS收集器
CMS收集器
Concurrent Mark Sweep 併發標記清除
標記-清除算法
與標記-壓縮相比
併發階段會降低吞吐量
老年代收集器(新生代使用ParNew)
-XX:+UseConcMarkSweepGC
CMS運行過程比較複雜,着重實現了標記的過程,可分爲
初始標記
根可以直接關聯到的對象
速度快
併發標記(和用戶線程一起)
主要標記過程,標記全部對象
重新標記
由於併發標記時,用戶線程依然運行,因此在正式清理前,再做修正
併發清除(和用戶線程一起)
基於標記結果,直接清理對象
特點
儘可能降低停頓
會影響系統整體吞吐量和性能
比如,在用戶線程運行過程中,分一半CPU去做GC,系統性能在GC階段,反應速度就下降一半
清理不徹底
因爲在清理階段,用戶線程還在運行,會產生新的垃圾,無法清理
因爲和用戶線程一起運行,不能在空間快滿時再清理
-XX:CMSInitiatingOccupancyFraction設置觸發GC的閾值
如果不幸內存預留空間不夠,就會引起concurrent mode failure
有關碎片
標記-清除和標記-壓縮
-XX:+ UseCMSCompactAtFullCollection Full GC後,進行一次整理
整理過程是獨佔的,會引起停頓時間變長
-XX:+CMSFullGCsBeforeCompaction
設置進行幾次Full GC後,進行一次碎片整理
-XX:ParallelCMSThreads
設定CMS的線程數量
GC參數整理
-XX:+UseSerialGC:在新生代和老年代使用串行收集器
-XX:SurvivorRatio:設置eden區大小和survivior區大小的比例
-XX:NewRatio:新生代和老年代的比
-XX:+UseParNewGC:在新生代使用並行收集器
-XX:+UseParallelGC :新生代使用並行回收收集器
-XX:+UseParallelOldGC:老年代使用並行回收收集器
-XX:ParallelGCThreads:設置用於垃圾回收的線程數
-XX:+UseConcMarkSweepGC:新生代使用並行收集器,老年代使用CMS+串行收集器
-XX:ParallelCMSThreads:設定CMS的線程數量
-XX:CMSInitiatingOccupancyFraction:設置CMS收集器在老年代空間被使用多少後觸發
-XX:+UseCMSCompactAtFullCollection:設置CMS收集器在完成垃圾收集後是否要進行一次內存碎片的整理
-XX:CMSFullGCsBeforeCompaction:設定進行多少次CMS垃圾回收後,進行一次內存壓縮
-XX:+CMSClassUnloadingEnabled:允許對類元數據進行回收
-XX:CMSInitiatingPermOccupancyFraction:當永久區佔用率達到這一百分比時,啓動CMS回收
-XX:UseCMSInitiatingOccupancyOnly:表示只在到達閥值的時候,才進行CMS回收
類加載器
class裝載驗證流程
加載
- 裝載類的第一個階段
- 取得類的二進制流
- 轉爲方法區數據結構
- 在Java堆中生成對應的java.lang.Class對象
鏈接
驗證
- 目的:保證Class流的格式是正確的
- 文件格式的驗證
- 是否以0xCAFEBABE開頭
- 版本號是否合理
- 元數據驗證
- 是否有父類
- 繼承了final類?
- 非抽象類實現了所有的抽象方法
- 字節碼驗證 (很複雜)
- 運行檢查
- 棧數據類型和操作碼數據參數吻合
- 跳轉指令指定到合理的位置
- 符號引用驗證
- 常量池中描述類是否存在
- 訪問的方法或字段是否存在且有足夠的權限
- 文件格式的驗證
- 目的:保證Class流的格式是正確的
準備
分配內存,併爲類設置初始值 (方法區中)
- public static int v=1;
- 在準備階段中,v會被設置爲0
- 在初始化的中才會被設置爲1
- 對於static final類型,在準備階段就會被賦上正確的值
- public static final int v=1;
解析
- 符號引用替換爲直接引用
- 初始化
- 執行類構造器
- static變量 賦值語句
- static{}語句
- 子類的調用前保證父類的被調用
- 是線程安全的
- 執行類構造器
什麼是類裝載器ClassLoader
- 概念
- ClassLoader是一個抽象類
- ClassLoader的實例將讀入Java字節碼將類裝載到JVM中
- ClassLoader可以定製,滿足不同的字節碼流獲取方式
- ClassLoader負責類裝載過程中的加載階段
JDK中ClassLoader默認設計模式
- ClassLoader的重要方法
/** 載入並返回一個Class*/
public Class<?> loadClass(String name) throws ClassNotFoundException;
/** 定義一個類,不公開調用*/
protected final Class<?> defineClass(byte[] b, int off, int len);
/**回調該方法,自定義ClassLoader的推薦做法*/
protected Class<?> findClass(String name) throws ClassNotFoundException loadClass;
/** 尋找已經加載的類*/
protected final Class<?> findLoadedClass(String name);
- 分類
- BootStrap ClassLoader (啓動ClassLoader)[rt.jar /-Xbootclasspath]
- Extension ClassLoader (擴展ClassLoader)[%JAVA_HOME%/lib/ext/*.jar]
- App ClassLoader (應用ClassLoader/系統ClassLoader)[Classpath下]
- Custom ClassLoader(自定義ClassLoader) [完全自定義路徑]
- 每個ClassLoader都有一個Parent作爲父親
- 問題
- 頂層ClassLoader,無法加載底層ClassLoader的類
- Java框架(rt.jar)如何加載應用的類?
- javax.xml.parsers包中定義了xml解析的類接口,Service Provider Interface SPI位於rt.jar ,即接口在啓動ClassLoader中。而SPI的實現類,在AppLoader。
- 解決
- Thread. setContextClassLoader()
- 上下文加載器
- 是一個角色
- 用以解決頂層ClassLoader無法訪問底層ClassLoader的類的問題
- 基本思想是,在頂層ClassLoader中,傳入底層ClassLoader的實例
- Thread. setContextClassLoader()
打破常規模式
- 雙親模式的破壞
- 雙親模式是默認的模式,但不是必須這麼做
- Tomcat的WebappClassLoader 就會先加載自己的Class,找不到再委託parent
- OSGi的ClassLoader形成網狀結構,根據需要自由加載Class
熱替換
- 類被加載後,無需重啓服務器,新加載的類即可直接使用
性能監控工具
系統性能監控
- 確定系統運行的整體狀態,基本定位問題所在
uptime
- 系統時間
- 運行時間
- 連接數
- 1,5,15分鐘內的系統平均負載
top
vmstat
pidstat
- 確定系統運行的整體狀態,基本定位問題所在
Java自帶的工具
jps
- 列出java進程,類似於ps命令
- 參數-q可以指定jps只輸出進程ID ,不輸出類的短名稱
- 參數-m可以用於輸出傳遞給Java進程(主函數)的參數
- 參數-l可以用於輸出主函數的完整路徑
- 參數-v可以顯示傳遞給JVM的參數
jinfo
- 可以用來查看正在運行的Java應用程序的擴展參數,甚至支持在運行時,修改部分參數
- -flag :打印指定JVM的參數值
- -flag [+|-]:設置指定JVM參數的布爾值
- -flag =:設置指定JVM參數的值
jmap
- 生成Java應用程序的堆快照和對象的統計信息
- jmap -histo 2972 >c:\s.txt
- Dump堆
- jmap -dump:format=b,file=c:\heap.hprof 2972
jstack
- 打印線程dump
- -l 打印鎖信息
- -m 打印java和native的幀信息
- -F 強制dump,當jstack沒有響應時使用
JConsole
- 圖形化監控工具
- 可以查看Java應用程序的運行概況,監控堆信息、永久區使用情況、類加載情況等
Visual VM
- Visual VM是一個功能強大的多合一故障診斷和性能監控的可視化工具
Java堆分析
內存溢出(OOM)的原因
- 可能發生內存溢出的內存空間
- 堆
- 永久區
- 用cglib生成大量的類,類的元信息是保存在永久區的
- 線程棧
- 這裏的棧溢出指,在創建線程的時候,需要爲線程分配棧空間,這個棧空間是向操作系統請求的,如果操作系統無法給出足夠的空間,就會拋出OOM
- 直接內存
- ByteBuffer.allocateDirect()無法從操作系統獲得足夠的空間
鎖
線程安全
- 多線程網站統計訪問人數
- 使用鎖,維護計數器的串行訪問與安全性
- 多線程訪問ArrayList[兩個線程同時往ArrayList中添加百萬元素,可能會發生越界異常]
對象頭Mark
- Mark Word,對象頭的標記,32位
- 描述對象的hash、鎖信息,垃圾回收標記,年齡
- 指向鎖記錄的指針
- 指向monitor的指針
- GC標記
- 偏向鎖線程ID
偏向鎖
- 大部分情況是沒有競爭的,所以可以通過偏向來提高性能
- 所謂的偏向,就是偏心,即鎖會偏向於當前已經佔有鎖的線程
- 將對象頭Mark的標記設置爲偏向,並將線程ID寫入對象頭Mark
- 只要沒有競爭,獲得偏向鎖的線程,在將來進入同步塊,不需要做同步
- 當其他線程請求相同的鎖時,偏向模式結束
-XX:+UseBiasedLocking
- 默認啓用
- 在競爭激烈的場合,偏向鎖會增加系統負擔
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
與-XX:-UseBiasedLocking
輕量級鎖
- BasicObjectLock
- 嵌入在線程棧中的對象
- 普通的鎖處理性能不夠理想,輕量級鎖是一種快速的鎖定方法。
- 如果對象沒有被鎖定
- 將對象頭的Mark指針保存到鎖對象中
- 將對象頭設置爲指向鎖的指針(在線程棧空間中)
- 如果輕量級鎖失敗,表示存在競爭,升級爲重量級鎖(常規鎖)
- 在沒有鎖競爭的前提下,減少傳統鎖使用OS互斥量產生的性能損耗
- 在競爭激烈時,輕量級鎖會多做很多額外操作,導致性能下降
自旋鎖
- 當競爭存在時,如果線程可以很快獲得鎖,那麼可以不在OS層掛起線程,讓線程做幾個空操作(自旋)
- JDK1.6中-XX:+UseSpinning開啓
- JDK1.7中,去掉此參數,改爲內置實現
- 如果同步塊很長,自旋失敗,會降低系統性能
- 如果同步塊很短,自旋成功,節省線程掛起切換時間,提升系統性能
幾種鎖的總結
- 不是Java語言層面的鎖優化方法
- 內置於JVM中的獲取鎖的優化方法和獲取鎖的步驟
- 偏向鎖可用會先嚐試偏向鎖
- 輕量級鎖可用會先嚐試輕量級鎖
- 以上都失敗,嘗試自旋鎖
- 再失敗,嘗試普通鎖,使用OS互斥量在操作系統層掛起
減少鎖持有時間
public synchronized void syncMethod(){
othercode1();
mutextMethod();
othercode2();
}
改爲:
public void syncMethod2(){
othercode1();
synchronized(this){
mutextMethod();
}
othercode2();
}
減小鎖粒度
- 將大對象,拆成小對象,大大增加並行度,降低鎖競爭
- 偏向鎖,輕量級鎖成功率提高
- ConcurrentHashMap
- HashMap的同步實現
- Collections.synchronizedMap(Map
鎖分離
- 根據功能進行鎖分離
- ReadWriteLock
- 讀多寫少的情況,可以提高性能
類型 | 讀鎖 | 寫鎖 |
---|---|---|
讀鎖 | 可訪問 | 不可訪問 |
寫鎖 | 不可訪問 | 不可訪問 |
- 讀寫分離思想可以延伸,只要操作互不影響,鎖就可以分離
- LinkedBlockingQueue
- 隊列
- 鏈表
鎖粗化
- 通常情況下,爲了保證多線程間的有效併發,會要求每個線程持有鎖的時間儘量短,即在使用完公共資源後,應該立即釋放鎖。只有這樣,等待在這個鎖上的其他線程才能儘早的獲得資源執行任務。但是,凡事都有一個度,如果對同一個鎖不停的進行請求、同步和釋放,其本身也會消耗系統寶貴的資源,反而不利於性能的優化
for(int i=0;i<CIRCLE;i++){
synchronized(lock){
}
}
改爲:
synchronized(lock){
for(int i=0;i<CIRCLE;i++){
}
}
鎖消除
- 在即時編譯器時,如果發現不可能被共享的對象,則可以消除這些對象的鎖操作
- 例如StringBuffer爲局部變量時,其內部的鎖實現其實是不需要的
- 參數
- -server -XX:+DoEscapeAnalysis -XX:+EliminateLocks 鎖消除
- -server -XX:+DoEscapeAnalysis -XX:-EliminateLocks 不使用鎖消除
無鎖
- 鎖是悲觀的操作
- 鎖是樂觀的操作
- 無鎖的一種實現方式
- CAS(Compare And Swap)
- 非阻塞的同步
- CAS(V,E,N)
- 在應用層面判斷多線程的干擾,如果有干擾,則通知線程重試
- java.util.concurrent.atomic.AtomicInteger
public final int getAndSet(int newValue) {
for (;;) {
int current = get();
if (compareAndSet(current, newValue))
return current;
}
}