進程是程序運行資源分配的最小單位
桌面雙擊啓動一個程序,該程序運行了一個進程,該進程裏有多個線程。
這些線程共享該進程中的所有系統資源。資源包括:CPU、內存空間、 磁盤 IO 等。而進程和進程之間是相互獨立的。
進程分爲系統進程和用戶進程。
線程是 CPU 調度的最小單位,必須依賴於進程而存在
線程自己擁有不多的資源,大部分都是共享進程所擁有的所有資源。java
多核心:也指單芯片多處理器。多個 CPU 同時並行地運行程序。
多線程: 讓同一個處理器上的多個線程同步執行並共享處理器的執行資源(併發)。面試
併發:指應用可以交替執行不一樣的任務。線程>核數的去執行
並行:指應用可以同時執行不一樣的任務。雙核cpu,兩線程分別去一個核執行算法
(1)充分利用 CPU 的資源
(2)加快響應用戶的時間
(3)可使你的代碼模塊化,異步化,簡單化
例如咱們實現電商系統,下訂單和給用戶發送短信、郵件就能夠進行拆分, 將給用戶發送短信、郵件這兩個步驟獨立爲單獨的模塊,並交給其餘線程去執行。 這樣既增長了異步的操做,提高了系統性能,又使程序模塊化,清晰化和簡單化。數據庫
(1)線程之間的安全性
在同一個進程裏面的多線程是資源共享的,也就是均可以訪問同一個內存地址當中的一個變量。編程
(2)線程之間的死鎖
爲了解決線程之間的安全性引入了 Java 的鎖機制。而鎖用的很差就容易死鎖,由於不一樣的線程都在等待那些根本不可能被釋放的鎖,從而致使全部的工做都沒法完成。設計模式
(3)線程太多了會將服務器資源耗盡造成死機當機
線程數太多有可能形成系統建立大量線程而致使消耗完系統內存以及 CPU的「過渡切換」,形成系統的死機。
解決方法:示例數據庫鏈接池。
只要線程須要使用一個數據庫鏈接,它就從池中取出一個,使用之後再將它返回池中。資源池也稱爲資源庫。數組
精華就是這個圖!!!!! 緩存
啥也不幹就啓動一個main(),java就會啓動好多線程。main()主線程、和衆多守護線程( 分發處理髮送給 JVM 信號的線程、GC線程、內存 dump線程等)安全
啓動線程的方式有兩種:
一、繼承Thread, X extends Thread;而後 X.start
二、實現Runnable,X implements Runnable;而後交給 Thread 運行服務器
Thread 和 Runnable 的區別:
Thread 纔是 Java 裏對線程的惟一抽象
Runnable 只是對任務(業務邏輯)的抽象
Thread 能夠接受任意一個 Runnable 的實例並執行。
天然終止
run 執行完成,或者拋出一個未處理的異常致使線程提早結束。
stop
暫停suspend()、恢復resume()和中止stop()。這三方法不建議使用。
緣由:這些方法是強制執行的。suspend、stop的時候。線程佔着資源(鎖和其餘資源等)直接睡眠,中止了,這樣會致使死鎖、內存泄露等等問題。
中斷 interrupt()
安全的停止則是其餘線程經過調用某個線程 A 的 interrupt()方法對其進行中斷操做。
中斷 interrupt()是一個中斷標誌位,只是通知一下線程應該中斷了,可是不會對線程有任何影響,而後根據代碼判斷控制是否該結束線程釋放資源。
由於只是一個標誌,因此當調用interrupt()方法時,不會影響改變線程的生命週期,線程能夠徹底不理會繼續執行,或者結束。
interrupt() 發起中斷請求,將
boolean isInterrupted() 判斷是否被中斷
boolean Thread.interrupted() 判斷當前線程是否被中斷,不過 Thread.interrupted()會同時將中斷標識位改寫爲 false
若是一個線程處於了阻塞狀態(如線程調用了 thread.sleep、thread.join、 thread.wait 等),則在線程在檢查中斷標示時若是發現中斷標示爲 true,則會在 這些阻塞方法調用處拋出 InterruptedException 異常,而且在拋出異常後會當即 將線程的中斷標示位清除,即從新設置爲 false。
不建議自定義一個取消標誌位來停止線程的運行。由於 run 方法裏有阻塞調 用時會沒法很快檢測到取消標誌,線程必須從阻塞調用返回後,纔會檢查這個取 消標誌。這種狀況下,使用中斷會更好,由於, 1、通常的阻塞方法,如 sleep 等自己就支持中斷的檢查, 2、檢查中斷位的狀態和檢查取消標誌位沒什麼區別,用中斷位的狀態還可 以免聲明取消標誌位,減小資源的消耗。
注意:處於死鎖狀態的線程沒法被中斷!!!
run()和 start() Thread類是Java裏對線程概念的抽象。
經過new Thread()其實只是 new 出一個 Thread 的實例,尚未操做系統中真正的線程掛起鉤來。
只有執行了 start()方法後,才實現了真正意義上的啓動線程。
start()方法不能重複調用,若是重複調用會拋出異常。
run() 方法是業務邏輯實現的地方,本質上和任意一個類的任意一個成員方 法並無任何區別,能夠重複執行,也能夠被單獨調用。
run()=普通方法
yield()
使當前線程讓出 CPU 佔有權,但讓出的時間是不可設定的。也不會釋放鎖資源。讓完以後該線程與其餘線程一塊兒競爭cpu使用權。
注意:並非每一個線程都須要這個鎖的,並且執行 yield( )的線 程不必定就會持有鎖,咱們徹底能夠在釋放鎖後再調用 yield 方法。 全部執行 yield()的線程有可能在進入到就緒狀態後會被操做系統再次選中 立刻又被執行。
join()
把指定的線程加入到當前線程,能夠將兩個交替執行的線程合併爲順序執行。
好比在線程 B 中調用了線程 A 的 Join()方法,直到線程 A 執行完畢後,纔會繼續 執行線程 B。(此處爲常見面試考點)
線程的優先級
setPriority()設置優先級,1~10,默認5。可是不一樣系統有不一樣的優先級,因此不可靠。聊勝於無
守護線程
線程分爲用戶線程和守護線程。本身啓動的叫用戶線程,後臺爲用戶線程服務的是守護線程。
Daemon(守護)線程是一種支持型線程,由於它主要被用做程序中後臺調度以及支持性工做。
當用戶線程全死亡時,他的守護線程也會死亡。
因此try{}finally{}
代碼塊,當線程爲守護線程時,他的用戶線程死亡,他被清除,不必定會執行finally{}
代碼塊,因此這種狀況下是不安全的。
能夠經過調用 Thread.setDaemon(true)
將線程設置爲 Daemon 線程。
wait()和 notify/notifyAll()
wait、notify是Object裏的方法。線程必需要得到該對象的對象級別鎖,即只能在同步方法或同步塊中調用 wait()方法、notify()系列方法--- 因此是經過某對象(Obj)來讓線程等待,更新的
必須放在同步方法裏(synchronized)
wait()調用後會釋放鎖資源!!!---由於別人要更新不放就死鎖了
notify()調用後不會釋放鎖資源---因此通常notify()放在代碼的最後一行,所有run()都跑完了,才釋放鎖 notify()隨機更新一個用到該對象的線程,notifyAll()通知全部線程
wait(long millis) ---等待超時就拋一個異常
是指一個線程 A 調用了對象 O 的 wait()方法進入等待狀態,而另外一個線程 B 調用了對象 O 的 notify()或者 notifyAll()方法,線程 A 收到通知後從對象 O 的 wait() 方法返回,進而執行後續操做。
上述兩個線程經過對象 O 來完成交互,而對象 上的 wait()和 notify/notifyAll()的關係就如同開關信號同樣,用來完成等待方和通 知方之間的交互工做。
標準範式
這圖不夠完善,有錯誤
補充完整版
只有synchronized纔會讓線程進入阻塞狀態,Lock讓鎖進入等待狀態。阻塞:被動進入(沒有搶到鎖)。等待:主動進入(wait、sleep等)
synchronized 內置鎖
Java 支持多個線程同時訪問一個對象或者對象的成員變量,關鍵字 synchronized 能夠修飾方法或者以同步塊的形式來進行使用,它主要確保多個線 程在同一個時刻,只能有一個線程處於方法或者同步塊中,它保證了線程對變量 訪問的可見性和排他性(原子性),又稱爲內置鎖機制。
synchronized叫作對象鎖,因此它鎖的是一個對象,能夠用在方法上或者代碼塊中。
可是也能用在static類(靜態類) 上,叫他 類鎖。實質鎖的是靜態類的Class對象。 因此仍是對象鎖。
錯誤使用
好比鎖Integer等對象時,失效。由於Integer等對象,實現i+1的時候,是將Integer->轉成int,而後 int+1在new 一個Integer賦值進去的,因此是新的對象。
volatile,最輕量的同步機制
volatile關鍵詞試用於一寫多讀
。它保證了可見性不保證原子性
volatile關鍵詞的做用是,一個線程裏修改,能通知其餘線程這個變量改變了。
ThreadLocal
ThreadLocal線程局部變量,在多線程試用的時候,每一個線程都有本身的這個變量。爲每一個線程提供變量副本/線程隔離
ThreadLocal 爲每一個線程都提供了變量的副本,使得每一個線程在某一時間訪問到的並不是同一個對象,這樣就隔離了多個線程對數據的數據共享。
ThreadLocal的使用,get、set、remove(刪除並回收資源)
使用案例: Spring 的事務就藉助了 ThreadLocal 類。Spring 會從數據庫鏈接池中得到一個 connection,然會把 connection 放進 ThreadLocal 中,也就和線程綁定了,事務需 要提交或者回滾,只要從 ThreadLocal 中拿到 connection 進行操做。
ThreadLocal的get方法 ThreadLocalMap方法
1.使用不當引起內存泄漏
看上圖和上上圖(ThreadLocalMap方法)得出
引用 ThreadLocal 的對象被回收了,可是 ThreadLocalMap 還持有 ThreadLocal 的引用,若是沒有手動刪除,ThreadLocal 的對象實例不會 被回收,致使 Entry 內存泄漏。(remove手動刪除,set、get方法可能會刪除一些key爲null的map)
因爲 ThreadLocalMap 的生命週期跟 Thread 同樣長,若是沒有手動刪除對應 key 就會致使內存泄漏。而不是由於弱引用,強引用必會發生內存泄露。因此取捨之間設計師選擇了弱引用
使用線程池+ ThreadLocal 時要當心,由於這種狀況下,線程是一直在不斷的重複運行的,從而也就形成了 value 可能形成累積的狀況。
2.錯誤使用 ThreadLocal 致使線程不安全
當設置爲static靜態時,全局共用一份變量,無法建變量副本。因此會致使ThreadLocal失效。你們都改的同一個變量致使線程不安全。
是指兩個或兩個以上的進程在執行過程當中,因爲競爭資源或者因爲彼此通訊而形成的一種阻塞的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖。
1.操做者≥2 && 資源≥2 && 操做者≥資源(1.多線程、2.多資源、3.多線程搶奪較少的資源)
2.爭奪資源的順序不對(1線程佔有1資源,2線程佔有2資源。而後1搶2,2搶1)
3.爭奪者拿到資源不放手。
學術化的定義
1)互斥:指進程對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個進程佔用。若是此時還有其它進程請求資源,則請求者只能等待,直至佔有資源的進程用畢釋放。
2)請求和保持:指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程佔有,此時請求進程阻塞,但又對本身已得到的其它資源保持不放。
3)不剝奪:指進程已得到的資源,在未使用完以前,不能被剝奪,只能在使用完時由本身釋放。
4)環路等待:指在發生死鎖時,必然存在一個進程——資源的環形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1佔用的資源;P1正在等待P2佔用的資源,……,Pn正在等待已被P0佔用的資源。
根據上面造成死鎖的緣由,能夠任選一個解決。
1.操做者、資源數量:沒法改變。
2.爭奪資源的順序不對。內部經過順序比較,肯定拿鎖的順序。
3.爭奪者拿到資源不放手。採用嘗試拿鎖的機制。(沒拿到鎖就釋放,用tryLock{}finally{釋放鎖})
嘗試拿鎖的機制
新啓兩線程,搶兩資源,1線程先搶1鎖後2,2線程先搶2鎖後1鎖
兩個線程在嘗試拿鎖的機制中,發生多個線程之間互相謙讓,不斷髮生同一個線程老是拿到同一把鎖,在嘗試拿另外一把鎖時由於拿不到,而將原本已經持有的鎖釋放的過程。
若是把上面例子。隨機睡眠的時間去掉,運行結果:
結果就是很長的拿鎖,釋放鎖,這也就是所謂的活鎖。線程12分別拿到12鎖,去取時發現沒有了,而後都一塊兒釋放了12鎖,再同步去取,又沒有鎖了,這樣無限循環。
各個線程取鎖的時間稍晚錯開一點。
低優先級的線程,老是拿不到執行時間
原子操做:不可分割的操做。 --- 從外部看該操做是不可分割的,要麼執行完,要麼不執行(synchronized就是原子操做)
想要實現原子操做,用鎖機制徹底能夠實現,可是synchronized太笨重了。想要高效的執行,CAS是一個不錯的選擇。
CAS現代CPU給的一個原子指令(i的內存地址,指望的值(舊值),新值) i跟舊值比較(compare),若是是舊值那就swap(交換)成新值。否的話就自旋(循環)
synchronized被稱爲悲觀鎖,CAS被稱爲樂觀鎖。
悲觀鎖:總感受有別的線程會改變個人值,因此我先搶佔鎖,而後修改。
樂觀鎖:不認爲有別的線程會改變個人值,因此我先修改,改完之後再去比較compare,若是以前沒有被修改那就交換swap。不然就一直循環。
CAS比synchronized快的關鍵是,synchronized在搶鎖的過程當中會頻繁的上下文切換。而CAS則是一直佔有着cpu進行自旋(無限循環)
解決:用版本戳,AtomicStampedReference(每次改動都有一個版本戳記錄版本號)或者AtomicMarkableReference(每次改動只告訴你有沒有改,沒有具體的版本號)
循環時間長開銷大。 這個無法解決,平常開放不會遇到,若是遇到就使用synchronized。
只能保證一個共享變量的原子操做。 CAS每次只能改變一個變量。
解決:將多個變量放到一個bean裏面
系統給咱們定義好了不少類,咱們直接用就行,凡是Atomic開頭的類,都是CAS
隊列
先進先出FIFO的線性表。
阻塞隊列
當隊列空去取的時候,或者隊列滿去插入的時候。隊列會阻塞線程,直到可操做。這樣的隊列稱爲阻塞隊列。
在併發編程中使用生產者和消費者模式可以解決絕大多數併發問題。該模式經過平衡生產線程和消費線程的工做能力來提升程序總體處理數據的速度。
生產者和消費者模式
原先生產和消費一一對應,很浪費資源,並且生產者速度過快,讓消費者來不及消費致使生產者等待。或者消費者過快,生產者來不及。這樣生產消費能力不均衡的問題很嚴重。
爲了解決生產消費能力不均衡的問題便有了生產者消費者模式。生產者和消費者模式是經過一個容器來解決生產者和消費者的強耦合問題。生產者和消費者彼此之間不直接通訊,而是經過阻塞隊列來進行通訊,因此生產者生產完數據以後不用等待消費者處理,直接扔給阻塞隊列,消費者不找生產者要數據,而是直接從阻塞隊列裏取,阻塞隊列就至關於一個緩衝區,平衡了生產者和消費者的處理能力。
BlockingQueue的方法
BlockingQueue阻塞隊列裏不只有阻塞方法,還有非阻塞方法,他們是成對出現的。
經常使用的阻塞隊列
● ArrayBlockingQueue:數組有界阻塞隊列。 (經常使用)
● LinkedBlockingQueue:鏈表有界阻塞隊列。 (經常使用)
區別:
1.鎖的實現不一樣。ArrayBlockingQueue生產和消費用的是同一個鎖。Linked兩個鎖。
2.在生產或消費時操做不一樣。ArrayBlockingQueue直接將枚舉對象插入或移除的。Linked把枚舉對象轉換爲Node進行插入或移除,會影響性能。
3.隊列大小初始化方式不一樣。Array必須指定初始化大小,Linked默認Integer.MAX_VALUE。
● SynchronousQueue:一個不存儲元素的阻塞隊列。
隊列大小1,現拿現取。
● PriorityBlockingQueue:優先級無界阻塞隊列。
● DelayQueue:優先級無界阻塞隊列實現的延時隊列。
是一個支持延時獲取元素的無界阻塞隊列。隊列使用PriorityQueue來實現。隊列中的元素必須實現Delayed接口,在建立元素時能夠指定多久才能從隊列中獲取當前元素。只有在延遲期滿時才能從隊列中提取元素。
● LinkedTransferQueue:鏈表無界阻塞隊列。
● LinkedBlockingDeque:鏈表雙向阻塞隊列。
有界無界
有界就是隊列長度有限。滿了之後就阻塞。
無界也會阻塞,由於取的時候隊列空。
本身作的時候選有界,由於無界容易擠爆資源。
第一:下降資源消耗。 經過重複利用已建立的線程下降線程建立和銷燬形成的消耗。
第二:提升響應速度。 當任務到達時,任務能夠不須要等到線程建立就能當即執行,減小線程建立,銷燬時間。
第三:提升線程的可管理性。 線程是稀缺資源,若是無限制地建立,不只會消耗系統資源,還會下降系統的穩定性,使用線程池能夠進行統一分配、調優和監控。
Executor(翻譯執行器)是一個接口,它是Executor框架的基礎,它將任務的提交與任務的執行分離開來。
ExecutorService接口繼承了Executor,在其上作了一些shutdown()、submit()的擴展,能夠說是真正的線程池接口;
AbstractExecutorService抽象類實現了ExecutorService接口中的大部分方法;
ThreadPoolExecutor是線程池的核心實現類,用來執行被提交的任務。
ScheduledExecutorService接口繼承了ExecutorService接口,提供了帶"週期執行"功能ExecutorService;
ScheduledThreadPoolExecutor是一個實現類,能夠在給定的延遲後運行命令,或者按期執行命令。 ScheduledThreadPoolExecutor比Timer更靈活,功能更強大。
不要返回值,用execute()
要返回值,submit(),submit會返回一個future對象
shutdown或shutdownNow。原理:遍歷工做線程,而後逐一調用interrupt方法來中斷線程,因此沒法響應中斷的任務可能永遠沒法終止。
區別:shutdown:啓動線程池的關閉序列。被關閉的執行其再也不接受新任務,所有任務執行完,線程死亡。 shutdownNow:取消全部還沒有開始的任務。
大量的計算,因此要減小上下文切換,最大線程數通常爲cpu的個數+1;
爲啥要+1呢? 由於操做系統會將一部分磁盤當內存來用,當任務存在磁盤裏,須要將磁盤裏讀取到內存,而後在交給cpu處理。讀取過程很慢,爲了充分利用cpu,就多一個線程,當在須要讀取的時候,就切換另外一個線程讓他跑在cpu。
獲取cpu個數方法:Runtime.getRuntime().availableProcessors()
2*n (cpu個數),由於io讀寫很慢,因此能夠多一些線程
當CPU密集型任務和IO密集型任務五五開的時候,應該拆分紅兩種線程池。當一個很小,一個很大時,能夠當作其中一種處理。
AbstractQueuedSynchronizer(翻譯 抽象、隊列、同步)隊列同步器(同步器或AQS)。是用來構建鎖或者其餘同步組件的基礎框架。 它使用了一個int成員變量(state)表示同步狀態,經過內置的FIFO隊列來完成資源獲取線程的排隊工做。
使用方式:子類經過繼承AQS並實現它的抽象方法來管理同步狀態。
(同步狀態:AQS裏由一個int型的state,好比鎖的狀態是否被持有)
(通常是自定義同步組件的靜態內部類繼承AQS)
(同步狀態重要的三方法:getState()、setState(int newState)和compareAndSetState(int expect,int update),如鎖0表示沒鎖,1表示有線程持有鎖,234表示有重入鎖-一個線程持好幾回鎖)
compareAndSetState方法 翻譯:比較而且設置state
AQS只是一個抽象類,自己沒有實現任何同步接口,他只是定義了若干同步狀態獲取和釋放的方法來供自定義同步組件使用(getState)。同步器既能夠支持獨佔式地獲取同步狀態,也能夠支持共享式地獲取同步狀態,這樣就能夠方便實現不一樣類型的同步組件(ReentrantLock、ReentrantReadWriteLock和CountDownLatch等)。
同步器是實現鎖(也能夠是任意同步組件)的關鍵,在鎖的實現中聚合同步器。
同步器和鎖:
鎖是面向使用者的,它定義了使用者與鎖交互的接口(好比能夠容許兩個線程並行訪問),隱藏了實現細節; 同步器面向的是鎖的實現者,它簡化了鎖的實現方式,屏蔽了同步狀態管理、線程的排隊、等待與喚醒等底層操做。鎖和同步器很好地隔離了使用者和實現者所需關注的領域。
鎖須要繼承同步器,並重寫指定的方法,隨後將同步器組合在自定義同步組件的實現中,並調用同步器提供的模板方法,而這些模板方法將會調用使用者重寫的方法。
同步器的設計基於模板方法模式。模板方法模式:定義一個操做中的算法的骨架,而將一些步驟的實現延遲到子類中。(也就是abstract抽像出一堆方法,讓子類去具體執行)
AQS中的方法主要分爲三類:獨佔式獲取與釋放同步狀態、共享式獲取與釋放、查詢同步隊列中的等待線程狀況。
AQS是根據CLH隊列鎖設計的。(CLH 三我的的名字的縮寫)
CLH隊列鎖也是一種基於鏈表的可擴展、高性能、公平的自旋鎖,申請線程僅僅在本地變量上自旋,它不斷輪詢前驅的狀態,假設發現前驅釋放了鎖就結束自旋。
新來一個等鎖的線程,就會被包裝成這樣的塊,而後排到隊列的末尾。因此這是一個先來後到的取鎖隊列,因此是公平鎖。
公平和非公平鎖
在ReentrantLock的構造函數中,有兩內置對象。NonfairSync對象(非公平鎖)和FairSync對象(公平鎖)。
JMM定義了Java 虛擬機(JVM)在計算機內存(RAM)中的工做方式。JVM是整個計算機虛擬模型,因此JMM是隸屬於JVM的。
a=a+1。這個簡單的運算,cpu在執行的過程當中,cpu從內存讀取數據須要100納秒,可是運算確只要0.6納秒。爲了解決這個讀取時間長的問題,現代CPU在其內部開闢了3個緩存空間。
L1,L2,L3高速緩存區
爲了適應cpu這種操做方式,java設計了JMM(java 內存模型)
JMM引起的線程安全問題
可見性
可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其餘線程可以當即看獲得修改的值。
原子性
原子性:即一個操做或者多個操做 要麼所有執行而且執行的過程不會被任何因素打斷,要麼就都不執行。
volatile還有抑制重排序(CPU運行代碼時,不是按照順序執行的,這樣很小可能會出現問題,用volatile就強制順序執行)。(cpu流水線等)
Synchronized在JVM裏的實現都是基於進入和退出 Monitor對象 來實現方法同步和代碼塊同步,雖然具體實現細節不同,可是均可以經過成對的MonitorEnter和MonitorExit指令來實現。
對同步塊,MonitorEnter指令插入在同步代碼塊的開始位置,當代碼執行到該指令時,將會嘗試獲取該對象Monitor的全部權,即嘗試得到該對象的鎖,而monitorExit指令則插入在方法結束處和異常處,JVM保證每一個MonitorEnter必須有對應的MonitorExit。
對同步方法,從同步方法反編譯的結果來看,方法的同步並無經過指令monitorenter和monitorexit來實現,相對於普通方法,其常量池中多了ACC_SYNCHRONIZED標示符。
JVM就是根據該標示符來實現方法的同步的:當方法被調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標誌是否被設置,若是設置了,執行線程將先獲取monitor,獲取成功以後才能執行方法體,方法執行完後再釋放monitor。在方法執行期間,其餘任何線程都沒法再得到同一個monitor對象。
synchronized使用的鎖是存放在Java對象頭裏面
對象的頭存儲的信息:鎖的狀態、hashCode、對象分帶年齡(GC次數所標記的老對象仍是新對象)、鎖標誌位等。
鎖一共有四種狀態:無鎖狀態,偏向鎖狀態,輕量級鎖狀態和重量級鎖狀態
Synchronized在設計的過程當中,由於太笨重了(全部線程阻塞、cpu頻繁的上下文切換-兩倍的次)等緣由優化了。將鎖設計成偏向鎖,輕量級鎖和重量級鎖三種狀態。
Synchronized關鍵詞下,(不少狀況是單線程在反覆的搶鎖放鎖,因此沒必要競爭,這時會使用)偏向鎖,可是一旦線程多了之後,競爭激烈了,偏向鎖就會膨脹成輕量鎖(相似CAS),當輕量鎖計算量很大,或者等待時間過長,超過一個時間片輪轉週期的時候,這樣還不如不用,就又會輕量鎖就會膨脹成重量鎖。鎖只能升級,不能降級。
無鎖狀態-->偏向鎖狀態-->輕量級鎖狀態-->重量級鎖狀態
引入背景:大多數狀況下鎖不只不存在多線程競爭,並且老是由同一線程屢次得到,爲了讓線程得到鎖的代價更低而引入了偏向鎖,減小沒必要要的CAS操做。
相似於CAS,由於上下文切換很費cpu時間(5000-20000)而計算只要0.6納秒,因此與其cpu一直上下文切換,還不如佔着CPU一直作自旋。
自旋鎖
先執行計算的代碼,而後在CAS(比較而且交換),若是是錯的(沒有搶到鎖),那就在重複回去初始化,計算,CAS。詳情見CAS。
優勢:對佔用鎖時間短的線程來講,提高很大
可是若是競爭大,持有鎖的時間長(超過一個上下文切換的時間),那就還不如用重量級鎖去阻塞線程了。由於自旋佔着CPU作無用功仍是挺浪費資源的。