1. 程式人生 > >高並發編程-04-線程的安全性

高並發編程-04-線程的安全性

再次 eight clas 什麽 都是 java 環境 如果 gin

之前,我們在前面已經介紹過了線程的安全性,本篇我們將繼續來深挖這個問題,繼續來探討什麽線程安全,原子性及加鎖機制。

1,什麽是線程安全?

線程安全,有兩個關鍵詞,“共享”和“可變”。

共享是指可以被多個線程同時訪問;

可變是指變量的值在生命周期內是可以變化的;

一個對象是否需要線程安全的,取決於它是否被多個線程訪問;

而如何保證一個對象的線程安全,則需要采用同步機制來協同對對象可變狀態的訪問。

下面,我們給線程安全下一個明確的定義:當多個線程訪問這個類時,如果這個類始終都能表現出正確的行為,那麽就稱這個類是線程安全的。

這裏有我經常喜歡問面試者的一個問題,Servlet是線程安全的嗎?

其實這個問題,並沒有標準答案。這個問題的關鍵是看Servlet是有狀態的,還是無狀態的。什麽叫有狀態的?簡單來說,就是在Servlet中定義了一個全局變量,然後在相關的doService或doGet、doPost等方法中,對這個全局變量做了更新操作,那麽這個時候就說明是有狀態的,反之就是無狀態的。而當有狀態時,就需要考慮線程安全的處理,否則不需要。

來,我們給大家一個結論:

無狀態的對象一定是線程安全的。

多線程環境下,多個線程共享一個資源,且進行的不是原子性操作,這個時候就要考慮線程的安全控制問題

2,原子性

我們在之前的文章也說過這個問題,比如一個簡單的操作,count++,其實是不具備原子性的,因為這個步驟實際會被拆分為三個步驟,即 讀取---修改---寫入,而這三個步驟有可能在某個時刻因CPU時間片的切換問題,而只執行其中一兩個步驟,這就不具備原子性。

在JDK中,為了解決這個問題,java.util.concurrent.atomic包提供了很多的類,來保證數據操作的原子性,比如我們之前的程序可以修改為

public class System {

private AtomicInteger integer = new AtomicInteger(0);

public int getCount(){

return integer.incrementAndGet();

}

}

3,鎖機制

通過上述的描述,我們知道,當一個類包含了一個有狀態的變量時,只要采用一個線程安全的對象就可以搞定這個問題,但是,在這裏要提醒大家一點,如果一個類包含了多個有狀態的變量,這個時候可不是單純的加幾個線程安全的對象就可以搞定問題。

一個判斷標準就是,只要程序中存在“先判斷,再更新”,那麽就要保證這兩個操作在一個原子操作裏面,才能保證線程安全。

關於java鎖機制的一些特點,我在這跟大家羅列下:

內置鎖、監視鎖、互斥鎖、可重入鎖都是在這個鎖的特點

內置鎖、監視鎖:這兩個說的是一個意思,java的每一個對象都可以用來做內置鎖,也就是為什麽我們的wait、notify方法定義在Object類的原因。

互斥鎖:表示最多只有一個線程可以持有這把鎖。

可重入鎖:是指當線程A請求一個由線程B持有的鎖時,線程B會進入阻塞狀態;而如果線程A如果再訪問另一段代碼,而這個代碼的鎖是已經被線程A持有的,這個時候請求是可以成功的,這就叫可重入。

跟大家講講這個鎖的實現原理:

JVM為每個鎖設置兩個屬性,獲取計數值和所有者線程,當計數值為0時,這個鎖就被認為是沒有被任何線程持有,當線程請求一個未被持有的鎖時,JVM將記錄鎖的持有者,並且計數值+1。如果同一個線程再次獲取這個鎖,則計數值將遞增,而當線程退出同步代碼塊時,計數器會相應遞減,當計數值為0,這個鎖將被釋放。

大家如果有問題,歡迎留言,留言我也不一定有時間回答。。。。。。

binary viewer 可以查看字節碼文件

通過javap -verbose *.class 也可以看到字節碼內容


高並發編程-04-線程的安全性