1.線程與進程
一個程序至少需要一個線程,一個進程至少需要一個線程 線程->進程->程序
線程是程序執行流的最小單位,進程是系統進行資源分配和調度的一個獨立單位。
2.Thread的幾個重要方法
①start()方法:開始執行該線程
②stop()方法:強制結束該線程
③join()方法 :等待該線程結束
④sleep()方法:該線程進入等待
⑤run()方法 :直接執行該線程的run方法(線程調用start()也會執行run方法,區別是一個是由線程調度運行run 方法,一個是直接調用線程中的run方法)
注意:wait()和notify()是object中的方法,分別表示線程掛起和線程恢復
wait()與sleep()的區別:wait()會釋放對象鎖,sleep()不會釋放對象鎖
3.線程狀態
線程共有5大狀態
①新建狀態:新建線程對象,並沒有調用start之前
②就緒狀態:調用start方法之后就進入就緒狀態,另外線程在睡眠和掛起中恢復的時候也會進入就緒狀態
③運行狀態:線程被設置為當前線程開始執行run方法
④阻塞狀態:線程被暫停。比如調用sleep方法后
⑤死亡狀態:線程執行結束
4.鎖類型
①可重入鎖:在執行對象中所有同步方法不用再次獲得鎖
②可中斷鎖:在等待獲取鎖過程中可中斷
③公平鎖:按等待獲取鎖的線程的等待時間進行獲取,等待時間長的具有優先獲取鎖的權力
④讀寫鎖:對資源讀取和寫入的時候拆分為2部分處理,讀的時候可以多線程一起讀,寫的時候必須同步的寫
java中每個對象都可作為鎖,鎖有四種級別,按照量級從輕到重分為:無鎖,偏向鎖,輕量級鎖,重量級鎖。每個對象一開始都是無鎖的,隨着線程間爭奪鎖,越激烈,鎖的級別越高,並且鎖只能升級不能降級。
鎖的實現機制與java對象頭息息相關,鎖的所有信息都記錄在java的對象頭中,用2字(32位JVM中1字=32bit)存儲對象頭,如果是數組類型使用3字存儲(還需要存儲數組長度),對象頭中記錄了hash值,GC年齡,鎖的狀態,線程擁有者,類元數據的指針
synchronized和lock的區別
類別 | synchronized | lock |
存在層次 | Java的關鍵字,在jvm層面上 | 是一個類 |
鎖的釋放 | 1.以獲取鎖的線程執行同步代碼,釋放鎖 2.線程執行發生異常,jvm會讓線程釋放鎖 |
在finally中必須釋放鎖,不然容易造成線程死鎖 |
鎖的獲取 | 假設A線程獲得鎖,B線程等待,如果A線程阻塞,B線程會一直等待 | 分情況而定,Lock有多個鎖獲取的方式,可以嘗試獲得鎖,線程可以不用一直等待 |
鎖狀態 | 無法判斷 | 可以判斷 |
鎖類型 | 可重入,不可中斷,非公平 | 可重入,可判斷,可公平(兩者皆可) |
性能 | 少量同步 | 大量同步 |
Lock接口的方法
①lock() :獲取鎖 如果鎖被暫用則一直等待
②unlock():釋放鎖
③trylock():返回類型是boolean,如果獲取鎖的時候鎖被占用就返回false,否則返回true
④tryLock(long time,TimeUnit unit):比起tryLock()就是給了一個時間期限,保證等待參數時間
⑤lockInterruptibly():用該鎖的獲得方式,如果線程在獲取鎖的階段進入了等待,那么可以中斷此線程,先去做別的事
5.兩種鎖的底層實現
synchronized:查看源碼可以知道synchronized映射成字節碼指令就是增加來拉個指令:monitorenter和monitorexit.當一條線程進行執行遇到monitorenter指令的時候,他會去嘗試獲得鎖,如果獲得鎖那么鎖計數+1,因為它是一個可重入鎖,所以需要用這個鎖計數判斷鎖的情況,如果沒有獲得鎖,那么阻塞,當它遇到monitorexit的時候,鎖計數-1,當計數器為0,就會釋放鎖。
synchronized鎖釋放有兩種機制,一種是執行完釋放,另外一種就是發送異常,虛擬機釋放,上圖的第二個monitorexit就是發生異常執行的流程,在13行的goto指令,意思是如果正常運行結束會跳轉到19行執行
lock:synchronized是一種悲觀鎖,每次都把自己關起來做事,怕被搶而lock底層是CAS樂觀鎖的體現,無所謂外界,如果被搶了,就重新去拿,很樂觀,底層主要靠volatile和CAS實現的
注意:盡可能去使用synchronized而不要去使用lock
在jdk1.6~jdk1.7的時候,也就是synchronized16、7年的時候,做了很多優化
6.synchronized作用
synchronized可以保證方法或者代碼塊在運行時同一時刻只有一個線程能進入臨界區,同時保證共享變量對其他線程的可見性
JDK1.6之前Synchronized是一個重量級鎖,是通過對象內部的一個叫做監視器鎖(monitor)來實現的,但是監視器本質又是依賴於底層的操作系統的Mutex Lock來實現的,而操作系統實現線程之間的切換這就需要從用戶態轉換到核心態
優化:
①線程自旋和適應性自旋
java線程其實是映射在內核之上的,線程的掛起和恢復會極大的影響開銷,並且jdk官方人員發現,很多線程在等待鎖的時候,在很短的一段時間就獲得了鎖,所以他們在線程等待的時候,並不需要把線程掛起,而是讓他無目的循環,一般設置10次,這樣就避免了線程切換的開銷,極大的提升了性能。而自適應自旋,是賦予自旋一種學習能力,它並不固定自旋10次一下。他可以根據它前面線程的自旋情況,從而調整它的自旋。甚至是不經過自旋而直接掛起
②鎖消除
鎖消除就是把不必要的同步在編譯階段進行移除,這里的鎖消除並不一定指你寫的代碼的鎖消除。
比如stringBuffer是一個安全的類,它在執行一個簡單的append方法拼接字符串,append方法都會同步,但分析可以知道這並不存在線程安全問題,這時就會把這個同步鎖消除
③鎖粗化
在用synchronized時,都講究避免大開銷,盡管同步代碼塊要小,為什么還要加粗呢。哪stringBuffer拼接字符串來說,每一個append都需要同步一次,那就可以把鎖粗化到第一個和最后一個append
④輕量級鎖
⑤偏向鎖