synchronizedjava
用來給對象和方法或者代碼塊加鎖,當它鎖定一個方法或者一個代碼塊的時候,同一時刻最多隻有一個線程執行這段代碼。當兩個併發線程訪問同一個對象object中的這個加鎖同步代碼塊時,一個時間內只能有一個線程獲得執行。另外一個線程必須等待當前線程執行完這個代碼塊之後才能執行該代碼塊。併發
要理解synchoronized原理,咱們先講講虛擬機(hotspot爲例,如下同)對象頭部分的佈局。對象頭分爲兩部分信息,第一部分用於存儲對象自身運行時的數據,如hashcode,GC分代年齡等,總計64位(64位虛擬機,32位虛擬機32位),官方成爲mark word,另外一部分用於存儲指向方法區的對象類型指針。性能
normal object headerspa
說是normal object header,是相對於加過偏向鎖的object(下文再說)。能夠看到對象頭裏面有1bit偏向鎖的描述,2bit鎖的描述。究竟是不是64位,咱們能夠經過Unsafe(一個頗有趣的類)來驗證下:.net
public class Cvolatile { private volatile boolean flag=false; //unsafe不能直接構造,經過反射獲得 public static Unsafe getUnsafe() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException{ Field field=Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); Unsafe unsafe=(Unsafe) field.get(null); return unsafe; } public static void headerByte() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException{ Unsafe unsafe=getUnsafe(); Field field=Cvolatile.class.getDeclaredField("flag"); System.out.println(unsafe.objectFieldOffset(field)); } public static void main(String args[]) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException{ headerByte(); } }
以上程序段新建了一個只有boolean變量的類,而後經過unsafe獲取了flag的偏移量,控制檯輸出是8,說明在64位jdk中object header的確是8*8便是64bit。線程
同步代碼塊是經過MonitorEnter和MonitorExit專用字節碼指令來實現,即在入口區加上MonitorEnter,在出口處加上MonitorExit。那JVM又是怎樣維護競爭同一個對象鎖的線程呢,如下貼出各大博客都有的流程:指針
Contention List:全部請求鎖的線程將被首先放置到該競爭隊列。code
Entry List:Contention List中那些有資格成爲候選人的線程被移到Entry List。orm
Wait Set:那些調用wait方法被阻塞的線程被放置到Wait Set。
OnDeck:任什麼時候刻最多隻能有一個線程正在競爭鎖,該線程稱爲OnDeck
Owner:得到鎖的線程稱爲Owner。
新請求鎖的線程將首先被加入到ConetentionList中,當某個擁有鎖的線程(Owner狀態)調用unlock以後,若是發現EntryList爲空則從ContentionList中移動線程到EntryList。ContentionList是個後進先出的隊列,ContentionList會被線程併發訪問,爲了下降對ContentionList隊尾的爭用,而創建EntryList。Owner線程在unlock時會從ContentionList中遷移線程到EntryList,並會指定EntryList中的某個線程(通常爲Head)爲Ready(OnDeck)線程。Owner線程並非把鎖傳遞給OnDeck線程,只是把競爭鎖的權利交給OnDeck,OnDeck線程須要從新競爭鎖。OnDeck線程得到鎖後即變爲owner線程,沒法得到鎖則會依然留在EntryList中,考慮到公平性,在EntryList中的位置不發生變化(依然在隊頭)。若是Owner線程被wait方法阻塞,則轉移到WaitSet隊列;若是在某個時刻被notify/notifyAll喚醒,則再次轉移到EntryList。
自旋鎖
基於互斥同步的鎖對性能最大的影響是阻塞的實現,掛起線程和恢復線程都須要裝入內核態中完成。同時,共享數據的鎖定狀態只會持續一會,爲了這段時間掛起和恢復線程都不值得。若是物理機器上有1個以上處理器,能讓2個或2個線程同時執行,咱們可讓後面請求鎖的線程"稍等一會「,但並不放棄處理器的執行時間,看看當前持有鎖的線程是否很快就會釋放鎖,爲了讓線程等待,咱們讓線程執行一個忙循環(能夠是執行幾條空的彙編指令,執行幾回循環,就是佔着CPU不幹事--),這就是所謂的自旋鎖。
synchronized中線程在進入ContentionList時首先進行自旋嘗試得到鎖,若是不成功再進入等待隊列。這對那些已經在等待隊列中的線程來講,稍微顯得不公平。
偏向鎖
偏向鎖就是爲提升性能在無競爭的狀況下把整個同步都給取消掉。它會偏向於第一個獲取它的線程,若是在接下來的執行過程當中,沒有其餘線程競爭這個鎖,則持有鎖的線程永遠不須要再進行同步。當虛擬機啓用偏向鎖,鎖對象第一次被線程獲取的時候,虛擬機會將biased lock設置爲1即偏向模式,同時使用CAS把得到鎖的線程ID寫入mark word,若是CAS操做成功,持有鎖的線程進入這個鎖相關的同步塊時候,虛擬機將不進行任何操做。
成功加了偏向鎖後的對象頭
—————————————————我是分割線—————————————————————
好玩的Unsafe類又出現了,咱們能夠利用Unsafe來實現簡單的sychronized功能
public static void csynchronized(Object syn) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, InterruptedException{ Unsafe unsafe=getUnsafe(); unsafe.monitorEnter(syn); for(int i=0;i<10;i++){ Thread.currentThread().sleep(100l); System.out.println(Thread.currentThread().getId()); } unsafe.monitorExit(syn); } public static void main(String args[]) { final Object syn=new Object(); new Thread(new Runnable(){ public void run() { try { csynchronized(syn); } catch (NoSuchFieldException e) { } catch (SecurityException e) { } catch (IllegalArgumentException e) { } catch (IllegalAccessException e) { } catch (InterruptedException e) { } } }).start(); new Thread(new Runnable(){ public void run() { try { csynchronized(syn); } catch (NoSuchFieldException e) { } catch (SecurityException e) { } catch (IllegalArgumentException e) { } catch (IllegalAccessException e) { } catch (InterruptedException e) { } } }).start(); }
從控制檯結果能夠看出線程ID之間並無穿插,說明實現了synchronized基本功能。固然咱們若是隻保留csynchronized中的for循環再次運行能夠發現線程ID之間有穿插。
———synchronized先到此,接下來將要說說Lock