標籤:http 9.png 多個 狀態 調用 代碼塊 維護 存在 哈哈
什麼是安全執行緒?
當多個線程訪問某個類時,不管這些的線程的執行順序如何,並且在主調代碼中不需要任何額外的同步或協同,這個類都能表現出正確的行為,那麼就稱這個類是安全執行緒的。
哈哈書上的解釋,還是翻譯過來的,看了半天還是覺得有點奇怪。比如說 “類都能表現出正確的行為” 是毛線意思?在網上搜了一番 "安全執行緒就是說多線程訪問同一代碼,不會產生不確定的結果" 這樣反而跟容易理解,果然讀書要麼讀原版中文書要麼讀原版英文書,看英文的翻譯版真的是蛋疼無比。說到這裡,我以前貌似把多線程及安全執行緒一個根本性問題搞混淆了:所謂的多線程是指多個線程跑同一段代碼,所以安全執行緒的概念是針對於這段代碼而言的而不是針對線程。
一個無狀態的Servlet
一個基於Servlet的因數分解服務,從請求中提取出數值進行因數分解,然後將結果封裝到該Servlet的響應中。這裡要理解的是什麼是這本書上說道的 “無狀態性” ?書上給的解釋是: 它即不包含任何域,也不包含任何對其他類中域的引用,計算過程中的臨時狀態僅存在於線程棧上的局部變數中。這裡的域是什麼意思?全域變數? 怎麼來理解這句話呢?大概意思就是對於多線程而言,一個類會同時有多個線程來訪問,使用裡面的方法,那麼這個類的全域變數對於這些線程來說實際上是同一個,只有一份來供這些線程操作,那麼線程不安全的問題就因此來了唄。而對於局部變數,不同的線程執行同一個類中的方法時,這個方法中的局部變數對於不同的線程來說都是單獨的一份,不存在資料共用的問題,所以無狀態對象一定是安全執行緒的。、
原子性
這個簡單,就是說一個操作是不可分割的,比如讀操作和寫操作,而對於下面的遞增操作++count就不是原子操作,其操作序列是:讀取-修改-寫入。
競態條件
由於不恰當的執行時序而出現的不正確的結果。說得通俗一點,就是線程A 需要判斷一個變數的狀態,然後根據這個變數的狀態來執行某個操作。在執行這個操作之前,這個變數的狀態可能會被其他線程使用,修改。所以是 "競爭狀態下的條件"
樣本:延遲初始化中的競態條件
目的是將對象初始化操作延遲到實際使用時才進行,同時要確保只被初始化一次。
複合操作
要避免競態條件問題,就必須在某個線程修改該變數時,通過某種方式防止其他線程使用這個變數,從而確保其他線程只能在修改操作完成之前或之後讀取和修改狀態,而不是在修改狀態的過程中。為了實現這種思路,必須將對同一個狀態的操作以原子的方式去執行,要麼全部執行完,要麼完全不執行。即將上面的 “先檢查後執行” 和 “讀取-修改-寫入” 等複合操作變為原子操作。
在java.util.concurrent.atomic包中包含了一些原子變數類,用於實現在數值和對象引用上的原子狀態轉換。通過用AtomicLong來代替long類型的計數器,能夠確保所有對計數器狀態的訪問操作都是原子的。使用安全執行緒對象(如AtomicLong)來管理類的狀態(這裡類的狀態好像指的就是類的全域變數啊,即在多線程操作時能被這些線程共同使用的變數叫做類的狀態變數?)
加鎖機制
單個狀態變數可以通過安全執行緒對象來管理Servlet的狀態以維護Servlet的執行緒安全性。但如果Servlet中有多個狀態變數,那麼是否只需添加更多的安全執行緒狀態變數就足夠了?答案肯定是否定的了。
關於AtomicReference和AtomicLong可以學習下這幾篇博文:Java多線程系列--“JUC原子類”04之 AtomicReference原子類 Java多線程系列--“JUC原子類”02之 AtomicLong原子類
上面的方法是不正確的,原因很好理解,原子狀態變數變數只能確保對這個狀態的操作是原子的,當有多個狀態變數時,無法保證對多個狀態的變數的操作是原子的。要保持狀態的一致性,就需要在單個原子操作中更新所有相關的狀態變數。
內建鎖
java提供的內建鎖機制是同步代碼塊,包括兩部分:一個作為鎖的對象的引用,一個作為有這個鎖保護的代碼塊。以關鍵子synchronized來修飾的方法是一種橫跨整個方法體的同步代碼塊,其中該同步代碼塊的鎖就是方法調用所在的對象。靜態synchronized方法以Class對象作為鎖。
現在讓我們來改進下之前的因數分解Servlet
重入
內建鎖是可重新進入的,意思就是如果某個線程試圖獲得一個已經由它自己持有的鎖,那麼這個請求就會成功而不會阻塞。重入的一種實現方法是:為每個鎖關聯一個擷取計數值和一個所有者線程。當計數值為0時就認為這個鎖沒有被任何線程持有。當線程請求一個未被持有的鎖時,JVM將記下鎖的持有人並且將擷取計數值置為1。如果同一個線程再次擷取這個鎖,計數值將遞增,而當線程退出同步代碼塊時,計數器會相應的遞減。當計數值為0時,這個鎖將被釋放。
用鎖來保護狀態
活躍性與效能
程式2-6的UnsafeCachingFactorizer的同步方式使得並發性十分糟糕。
可以通過縮小同步代碼塊的作用範圍,來確保並發性和執行緒安全性。
註:當執行時間較長的計算或操作時(如:網路, I/O 等),一定不要持有鎖
《java並發編程實戰》讀書筆記1--執行緒安全性,內建鎖,重入,狀態