當多個線程涉及到共享數據的時候,就會設計到線程安全的問題。非線程安全其實會在多個線程對同一個對象中的實例變量進行並發訪問時發生,產生的后果就是“臟讀”。發生臟讀,就是取到的數據已經被其他的線程改過了。什么是線程安全呢?用並發編程實戰里面的一段話解釋說:
當多個線程訪問某個類時,不管運行時環境采用何種調度方式或者這些線程將如何交替執行,並且在主調代碼中不需要額
外的同步或協同,這個類都能表現出正確的行為,那么就稱這個類是線程安全的
這里需要注意的是多個線程,如果一個線程肯定是線程安全的,而且這里的共享數據是指成員變量,不是局部變量,局部變量是
方法私有的,而方法運行時,對應的虛擬機方法棧是線程私有的,所以局部變量一定是方法安全的。
為了保證線程的安全,就要用到同步了。同步可以這么理解,只有等一個線程執行完這么一段需要同步的代碼,其他的線程才能執行。而異步就是這段代碼代碼可以交替執行。
1.synchronized同步方法
synchronized同步方法的用法是
synchronized 修飾符 返回值 方法名(){ }
1.synchronized取得的鎖都是對象鎖,而不是把一段代碼或函數當做鎖
synchronized方法相當於給這個方法上了一把鎖,鎖就是擁有這個方法的實例對象,當多個線程訪問一個類的同一個實例對象時,這個鎖也就是這個實例對象,先獲得這把鎖的線程就可以執行同步方法里面的內容,其他線程只有等第一個線程執行結束自動釋放鎖或者程序拋出異常或者使用wait()等方法釋放鎖的情況下才能獲得鎖。
當多個線程訪問一個類的多個實例對象時,jvm就創建了多把鎖,多個線程獲取到的鎖不一樣。這時候同步方法還是異步執行的。
2.synchronized方法鎖重入
鎖重入的意思是,一個線程已經擁有了這個對象的鎖,再次請求該對象鎖時,還是會保證成功,也就是說,在synchronized方法里面,再調用本類中的其他的synchronized方法,是永遠可以得到鎖的。否則,會造成死鎖。
3.出現異常鎖會自動釋放
4.同步不具有繼承性
也就是說父類中方法是同步的,子類繼承父類的方法,這個方法就不是同步的了,需要再加上synchroized變成同步方法
5.如果多個線程持有一把鎖,也就是只有一個實例對象,那么該對象里面的所有synchroized方法都具有同步性,也就是,當一個線程調用其中一個sycnhroized方法時,其他線程調用這個對象里面的其他synchroized也會處於阻塞狀態。
使用synchroized方法有什么弊端呢?從運行時間來看,當一個線程取得鎖以后,其他線程只有等待它釋放鎖以后才能執行方法里面的代碼,從運行時間來看,這樣會浪費很長的時間,怎么改變呢?就要用到同步語句塊。
2.同步代碼塊
同步代碼塊如何解決上面問題呢?那就是只將需要同步的方法用
synchroized(this|任意對象|class){
}
括起來。括號里面的內容是一個監視器
只有代碼塊里面的代碼是同步的,其余的代碼還是異步的。
一、
1.當括號里面用this時,鎖定的也是當前對象。
這時候其實和使用synchroized方法一樣。
2.當括號里面是任意對象時。
當多個線程持有的對象監視器為同一個的前提下,如上。
但是當多個線程持有對象監視器為多個時,由於對象監視器不同,所以運行結果就是異步的。同步代碼塊放在非同步synchronized方法中進行生命,並不能保證調用方法的線程的執行同步/順序性,也就是線程調用方法的順序是無需的,雖然在同步塊中執行的順序是同步的,這樣極其容易出現臟讀。
所以最好保證對象監視器是同一個對象。鎖非this對象具有的優點是:如果在一個類中由很多個synvhronized方法,這時雖然能實現同步,但是會阻塞,所以影響效率;如果使用同步代碼塊鎖非this對象,則同步代碼塊中的程序與同步方法時異步的,不予其他鎖this同步方法爭搶this鎖,大大提高運行效率。
3.括號里面是類時,靜態同步方法一樣。
靜態同步方法其實是給類上鎖,和普通的同步方法持有的不是一個鎖。一個是類鎖,一個是對象鎖。
同步synchroized(Class)作用也是如此,但是對這個類的任何實例都起作用。
二、synchroized(任意對象)的三個結論
1.多個線程同時執行時呈同步效果
2.當其他線程執行任意對象中synchronized同步方法時呈同步效果
3.當其他線程執行任意對象里面的synchronized(this)代碼塊時呈同步效果
三、String的常量池特性
Jvm中有String常量池緩存的功能。
如果使用synchronized(string )時需要注意。“AA”與“AA”是相同的,也是如果兩個同步塊都使用AA,這兩個代碼塊的鎖時一樣的。
四、snchroized方法無線等待與解決。
這里就是如果在snchroized方法中設置一個死循環,其他synchronized方法將無法獲得執行機會。
而使用snchroized(任意對象)就可以解決這個問題
五、死鎖
死鎖怎么理解呢?大概就是“你先給我我就給你”“不,你給我我才給你”,兩個人都不想讓,就造成了這種死循環。
線程中的死鎖通俗點講就是互相等待對方先釋放鎖。不是只有一個鎖里面嵌套着另外一個鎖才會出現死鎖
六、鎖對象的改變。
當鎖在運行中改變時,只要對象不變,即使對象的屬性改變,那么鎖還是沒有改變。
三、volatile關鍵字
1.volatile關鍵字與死循環
想一想,如果你設置了一個boolean類型的變量,並在方法中設置一個while循環,循環的條件是這個boolean變量,如果運行中改變了這個變量的值,while會停下來么?答案是不會。
如果改由線程的方式呢?
但是在-server服務器模式中還是會死循環。什么原因呢?因為boolean變量的值存在於主內存及線程的工作內存中。在jvm被設置為-server模式時為了線程運行的效率,線程一直在工作內存中取值。改變boolean值得操作雖然被執行,更新的是主內存中的變量值,所以一直就是死循環的狀態
這時候就可以用到volidate。
2.volidate非原子性
volidate只是強迫線程每一次取值的時候都從主內存中取,只具備可見性,並不具備同步性,那么也就不具備原子性。
3.原子操作
原子操作是不可分割的整體,沒有其他線程能夠中斷或者檢查正在原子操作中的變量。一個原子類型就是一個原子操作可用的對象。
下面是原子類。
1 AtomicBoolean -- 原子布爾 2 AtomicInteger -- 原子整型 3 AtomicIntegerArray -- 原子整型數組 4 AtomicLong -- 原子長整型 5 AtomicLongArray -- 原子長整型數組 6 AtomicReference -- 原子引用 7 AtomicReferenceArray -- 原子引用數組 8 AtomicMarkableReference -- 原子標記引用 9 AtomicStampedReference -- 原子戳記引用 10 AtomicIntegerFieldUpdater -- 用來包裹對整形 volatile 域的原子操作 11 AtomicLongFieldUpdater -- 用來包裹對長整型 volatile 域的原子操作 12 AtomicReferenceFieldUpdater -- 用來包裹對對象 volatile 域的原子操作
4.原子類也並不完全安全
當原子類在具有邏輯性的操作下也會具有隨機性。
因為原子類的方法雖然是原子的,但是方法與方法之間卻不是原子的。解決的方法就是必須使用同步
4.synchronized代碼塊由volidate同步的功能
關鍵字synchronized保證在同一時刻,只有一個線程可以執行某一個方法或某一個代碼塊。包含兩個特征:互斥性和可見性。