當多個線程訪問一個對象時,若是不考慮這些線程在運行環境下的調度和交替執行,也不須要進行額外的同步,或者在調用地方進行額外的協調操做,調用這個對象的行爲均可以得到正確的結果,那麼這個對象是線程安全的。web
各類操做共享的數據分類5類:安全
互斥是實現同步的一種手段。須要阻塞和喚醒,使得性能下降,也稱阻塞同步,是悲觀鎖。不管共享數據是否會發生競爭都須要加鎖。服務器
最基本互斥手段是synchronized。會在同步塊的先後造成monitorenter和monitorexit,在執行monitorenter時嘗試去獲取鎖,若是已被本身持有或者未被持有則鎖計數器加1,執行monitorexit就會減1,直到爲0才釋放鎖。若是獲取鎖失敗則會處於阻塞狀態。多線程
除了synchronnized外還有JUC的重入鎖(ReentrantLock),都具有線程重入特性,只是代碼寫法上有區別,一個爲API層面的互斥鎖(lock和unlock方法配合try/finally語句實現),一個是原生語法層面的互斥鎖。RenetrantLock多了3個高級功能:併發
樂觀鎖。其併發策略時先進行操做,若是沒有線程用共享數據,那操做就成功;若是有爭用,那麼產生衝突,就採起其它措施補償(常見的補償是不斷重試直到成功爲止)。所以就不要把線程掛起。app
樂觀鎖須要保證操做和衝突檢測具備原子性,若是使用互斥同步來保證就失去意義了,所以須要硬件來完成這件事。如CAS指令。性能
CAS有三個操做數:內存地址A、舊的預期值O、新值N。當且僅當A符合舊預期值O時才進行更新V爲新值N,並返回舊值O,若是不符合則不進行更新。該操做是一個原子操做。優化
保證線程安全不必定就須要同步,兩者無因果關係。若是一個方法不涉及共享數據,那麼就無須任何同步。例以下面兩類代碼是天生線程安全的:spa
鎖優化技術:自適應自旋、鎖消除、鎖粗化、輕量級鎖、偏向鎖等、操作系統
互斥同步最大的性能消耗就是線程的掛起和恢復,都須要轉入內核態完成。大部分共享數據的鎖定狀態只持續很短的時間,所以爲了這段時間取掛起和恢復不值得,因此可讓後面請求的鎖「等待一下」,但不放棄處理器的執行時間,只需執行忙循環(自旋),這就是所謂的自旋鎖。
自旋不能代替阻塞。若是自旋的時間開銷超過了線程掛起和恢復的時間開銷,那麼就白白消耗處理器資源。默認自旋10次,可-XX:PreBlockSpin更改。JDK1.6後引入自旋鎖,自旋時間不是固定的,而是由上一次在同一個鎖上的自旋時間以及鎖的擁有者狀態來決定。若是上一次自旋成功且線程正在運行中,則會認爲這次自旋可能成功,所以會讓自旋持續更長時間,若是一個鎖的自旋不多成功則之後獲取可能直接省略自旋直接掛起。
鎖消除是指在即時編譯時,對一些代碼上要求同步,可是被檢測到不存在共數據競爭的鎖進行消除。例如StringBuffer的append有同步塊,其引用sb不會逃逸到concatString外,其餘線程沒法訪問到sb來進行操做,所以鎖能夠被安全消除。sb放到實例變量去就得加鎖處理。
public String concatString(String s1, String s2, String s3) { StringBuffer sb = new StringBuffer(); sb.append(s1); sb.append(s2); sb.append(s3); return sb.toString(); }
原則上是推薦將同步的做用範圍變小,只在共享數據的實際做用域才發生同步。但若是在一系列操做對同一個對象反覆加鎖解鎖,甚至加鎖出如今for循環裏,那麼即便沒競爭,頻繁的互斥同步也會致使性能損耗。例如上面的append,虛擬機就會把加鎖範圍變成第一個append擴展到最後一個append。
輕量級鎖是相對使用操做系統互斥量來實現的傳統鎖而言的,傳統鎖是重量級鎖。輕量級鎖是減小使用操做系統的互斥量產生的性能損耗。(使用自旋鎖)
若是一開始線程獲取鎖時,對象頭標誌未鎖定,則用CAS操做使對象頭標誌爲輕量鎖,則擁有了該對象的鎖。若是更新失敗,則判斷對象的mark word是否指向當前線程的棧幀,是則代表已經擁有這個鎖,直接進入同步代碼塊,不然說明該鎖被其它線程佔了。若是兩條以上的線程爭同一個鎖,那麼輕量級鎖就變成重量級鎖。
輕量級鎖提高同步性能的依據是「大部分鎖,在整個同步週期是不存在競爭的」,這是一個經驗數據。沒有競爭,則使用CAS操做就避免了使用互斥的開銷。
鎖會偏向於第一個獲取它的線程,若是在接下來的執行過程當中,鎖沒被其它線程獲取,則持有偏向鎖的線程不須要再進行同步。當另一個線程嘗試獲取該鎖時,則根據鎖對象是否被鎖定轉換爲輕量級鎖或未鎖定狀態。