一. 對(duì)象及變量的并發(fā)訪問(wèn)
非線程安全會(huì)發(fā)生在多個(gè)線程并發(fā)訪問(wèn)同一個(gè)對(duì)象的實(shí)例變量時(shí)身弊,會(huì)產(chǎn)生臟讀,讀取到的數(shù)據(jù)是被更改過(guò)的吕漂。而線程安全各個(gè)線程獲得的實(shí)例變量的值都是經(jīng)過(guò)同步處理的廷痘,不會(huì)出現(xiàn)臟讀。
1.線程是否安全呢构灸?
(1) 如果是方法內(nèi)部的私有變量上渴,不存在非線程安全問(wèn)題。
(2) 多線程訪問(wèn)的是同一個(gè)對(duì)象的實(shí)例變量時(shí),有可能出現(xiàn)線程不安全問(wèn)題稠氮。
(3) 多個(gè)線程訪問(wèn)的是同步方法的話曹阔,一定是線程安全的。
(4) 多個(gè)線程對(duì)應(yīng)的是多個(gè)對(duì)象時(shí)隔披,出現(xiàn)的結(jié)果就會(huì)是異步的赃份,但是線程安全。
2. synchronized關(guān)鍵字
(1) synchronized獲得的是對(duì)象鎖奢米,而不是把synchronized下面的方法或者代碼塊當(dāng)做鎖抓韩。
(2) synchronized聲明的方法一定是排隊(duì)執(zhí)行的(同步的),只有共享資源的讀寫(xiě)訪問(wèn)才需要同步化恃慧。
(3) 線程A和線程B訪問(wèn)同一個(gè)object對(duì)象的兩個(gè)同步的方法园蝠,線程A先獲取object對(duì)象的Lock鎖,B線程可以以異步的方式調(diào)用object對(duì)象中的非同步方法痢士,但是想訪問(wèn)該對(duì)象的同步方法的話彪薛,必須得等待,不管想訪問(wèn)的是不是和線程A同一個(gè)同步方法怠蹂。
(4) synchronized有鎖重入的功能善延,即自己可以再次獲取自己的內(nèi)部鎖,可重入鎖也支持父子類(lèi)繼承關(guān)系中城侧,即子類(lèi)可以通過(guò)可重入鎖訪問(wèn)父類(lèi)的方法易遣。若不可重入的話,就會(huì)造成死鎖嫌佑。
(5) 當(dāng)一個(gè)線程執(zhí)行的代碼出現(xiàn)異常時(shí)豆茫,其持有的鎖會(huì)自動(dòng)釋放(即該線程結(jié)束執(zhí)行)。
(6) 同步不具有繼承性屋摇。
(7) 鎖定的對(duì)象改變揩魂,比如String,可能導(dǎo)致同步鎖無(wú)效(因?yàn)殒i變了)炮温。但是只要對(duì)象不變火脉,對(duì)象的屬性被改變,鎖還是同一個(gè)柒啤。
3.synchronized同步語(yǔ)句塊
synchronized同步方法是對(duì)當(dāng)前對(duì)象加鎖倦挂,同步代碼塊則是對(duì)某一個(gè)對(duì)象加鎖。synchronized同步代碼塊運(yùn)行效率應(yīng)該大于同步方法担巩。
synchronized(this):也是鎖定當(dāng)前對(duì)象的方援。
synchronized(非this對(duì)象):使用同步代碼塊來(lái)鎖定非this對(duì)象,則synchronized(非this對(duì)象)與同步方法是異步的涛癌,不與其他鎖this同步方法爭(zhēng)搶this鎖肯骇,可以大大提高效率窥浪。synchronized同步代碼塊都不采用String作為鎖對(duì)象,易造成死鎖笛丙。
4.synchronized關(guān)鍵字加到static靜態(tài)方法上是給Class類(lèi)加上鎖(Class鎖可以對(duì)類(lèi)得所有對(duì)象實(shí)例起作用),而加到非static靜態(tài)方法上是給對(duì)象上鎖假颇。
synchronized關(guān)鍵字加到static靜態(tài)方法上是給Class類(lèi)加上鎖 = synchronized(xxx.Class){}
5.多線程的死鎖
因?yàn)椴煌木€程都在等待根本不可能被釋放的鎖胚鸯,從而導(dǎo)致所有的任務(wù)都無(wú)法繼續(xù)執(zhí)行。
比如線程A持有了鎖1在等待鎖2笨鸡,線程A持有了鎖2在等待鎖1--》導(dǎo)致死鎖姜钳。
解決方案:不使用嵌套的synchronized代碼結(jié)構(gòu)。
6.內(nèi)置類(lèi)與靜態(tài)內(nèi)置類(lèi)(補(bǔ)充介紹)
非靜態(tài)內(nèi)置類(lèi):指定對(duì)象.new 內(nèi)置類(lèi)();
靜態(tài)內(nèi)置類(lèi):可直接new 內(nèi)置類(lèi)();
7.volatile關(guān)鍵字:使變量在多個(gè)線程中可見(jiàn)
作用:強(qiáng)制從公共堆棧中獲取變量的值形耗,而不是從線程私有數(shù)據(jù)棧中獲取哥桥。
※ 在JVM被設(shè)置為-server模式時(shí)是為了線程運(yùn)行的效率,線程一直在私有堆棧中獲取變量的值激涤。
在-server模式下拟糕,公共堆棧的值和線程私有數(shù)據(jù)棧的值不同步,加了volatile后就會(huì)強(qiáng)制從公共堆棧中讀寫(xiě)倦踢。
※volatile和synchronized的比較:
(1) volatile只能修飾變量送滞,是輕量級(jí)實(shí)現(xiàn),所以性能比synchronized好辱挥。
(2) 多線程訪問(wèn)volatile不會(huì)阻塞犁嗅,而訪問(wèn)synchronized會(huì)阻塞。
(3) volatile能保證數(shù)據(jù)可見(jiàn)性晤碘,但不具備同步性褂微,不支持原子性;而synchronized可以保證原子性园爷,也可以間接保證可見(jiàn)性宠蚂,因?yàn)樗麜?huì)將私有內(nèi)存和共有內(nèi)存中的數(shù)據(jù)做同步。
(4) 兩者功能屬性不同腮介,synchronized解決的是多個(gè)線程之間訪問(wèn)資源的同步性肥矢;
volatile解決變量在多個(gè)線程之間的可見(jiàn)性,即:在多個(gè)線程可以感知實(shí)例變量被修改了叠洗,并且可以獲得最新的值引用甘改,也就是用多線程讀取共享變量時(shí)能獲得最新值引用。
volatile int i
i++;
i++有如下三個(gè)步驟:
(1) 從內(nèi)存中獲取i的值灭抑;
(2)計(jì)算i的值十艾;
(3) 將i的值寫(xiě)入內(nèi)存中。
這樣的操作不是一個(gè)原子操作(聯(lián)想:synchronized修飾的方法或者代碼段可以看做一個(gè)整體腾节,因此具有原子性)忘嫉,比如線程B要提取i的值時(shí)荤牍,線程A還未將計(jì)算好的i的值放回內(nèi)存,則線程B取出來(lái)的i的值還是線程A計(jì)算前的值庆冕。--》線程不安全
8.AtomicInteger(AtomicLong等)
private AtomicInteger count = new AtomicInteger(0);
System.out.println(count.incrementAndGet());//自動(dòng)加1//decrementAndGet()自動(dòng)減1
public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } }
Compare And Swap(CAS):首先康吵,CPU 會(huì)將內(nèi)存中將要被更改的數(shù)據(jù)與期望的值做比較。然后访递,當(dāng)這兩個(gè)值相等時(shí)晦嵌,CPU 才會(huì)將內(nèi)存中的數(shù)值替換為新的值。否則便不做操作拷姿。最后惭载,CPU 會(huì)將舊的數(shù)值返回。這一系列的操作是原子的响巢。簡(jiǎn)單來(lái)說(shuō)描滔,CAS 的含義是“我認(rèn)為原有的值應(yīng)該是什么,如果是踪古,則將原有的值更新為新值含长,否則不做修改,并告訴我原來(lái)的值是多少”灾炭。
CAS有3個(gè)操作數(shù)茎芋,內(nèi)存值V,舊的預(yù)期值A(chǔ)蜈出,要修改的新值B田弥。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為B铡原,否則返回V偷厦。這是一種樂(lè)觀鎖的思路,它相信在它修改之前燕刻,沒(méi)有其它線程去修改它只泼;而Synchronized是一種悲觀鎖,它認(rèn)為在它修改之前卵洗,一定會(huì)有其它線程去修改它请唱,悲觀鎖效率很低。下面來(lái)看一下AtomicInteger是如何利用CAS實(shí)現(xiàn)原子性操作的过蹂。
但是CAS也是有問(wèn)題存在的:
CAS的ABA問(wèn)題
1.進(jìn)程P1在共享變量中讀到值為A
2.P1被搶占了十绑,進(jìn)程P2執(zhí)行
3.P2把共享變量里的值從A改成了B,再改回到A酷勺,此時(shí)被P1搶占本橙。
4.P1回來(lái)看到共享變量里的值沒(méi)有被改變,于是繼續(xù)執(zhí)行脆诉。
雖然P1以為變量值沒(méi)有改變甚亭,繼續(xù)執(zhí)行了贷币,但是這個(gè)會(huì)引發(fā)一些潛在的問(wèn)題。ABA問(wèn)題最容易發(fā)生在lock free 的算法中的亏狰,CAS首當(dāng)其沖役纹,因?yàn)?b>CAS判斷的是指針的地址。如果這個(gè)地址被重用了呢暇唾,問(wèn)題就很大了字管。(地址被重用是很經(jīng)常發(fā)生的,一個(gè)內(nèi)存分配后釋放了信不,再分配,很有可能還是原來(lái)的地址)
還有一種情況是:?jiǎn)为?dú)一個(gè)AtomicInteger.incrementAndGet()是線程安全的亡呵,但是同時(shí)兩個(gè)AtomicInteger.incrementAndGet()就不一定是線程安全的了抽活,即兩個(gè)方法之間不是原子的。
public static AtomicLong count = new AtomicLong();
public void addNum(){
System.out.println(count.addAndGet(100));
System.out.println(count.addAndGet(1));
}
多個(gè)線程調(diào)用addNum()時(shí)锰什,線程A加了100下硕,還沒(méi)來(lái)得及加1,線程B就進(jìn)來(lái)加了100汁胆。
解決方案:
public static AtomicLong count = new AtomicLong();
synchronized public void addNum(){
System.out.println(count.addAndGet(100));
System.out.println(count.addAndGet(1));
}
9.synchronized代碼塊也具有volatile同步的功能
線程A調(diào)用runMethod()梭姓,線程B調(diào)用stopMethod(),持有的是同一把鎖嫩码。
當(dāng)線程A調(diào)用完runMethod()后誉尖,打印不出"停下來(lái)了!"的铸题,因?yàn)樗姥h(huán)铡恕,被A鎖死了。
各線程間的數(shù)據(jù)值沒(méi)有可見(jiàn)性丢间。
private Boolean isContinueRun = true;
public void runMethod(){
??? while(isContinueRun){
??? }
??? System.out.println("停下來(lái)了探熔!");
}
public void stopMethod(){
??? isContinueRun = false;
}
解決方案如下,成功打印"停下來(lái)了烘挫!"
private Boolean isContinueRun = true;
public void runMethod(){
??? private anyString = new String();
??? while(isContinueRun){
??????? synchronized(anyString){
??????? }
??? }
??? System.out.println("停下來(lái)了诀艰!");
}
public void stopMethod(){
??? isContinueRun = false;
}
關(guān)鍵字synchronized 保證同一時(shí)刻,只有一個(gè)線程可以執(zhí)行某一個(gè)方法或某一個(gè)代碼塊饮六。包含兩種特性:互斥性和可見(jiàn)性其垄。
?不僅可以解決一個(gè)線程看到對(duì)象處于不一致的狀態(tài),還可以保證進(jìn)入同步方法或代碼塊的每個(gè)線程喜滨,都可以看到由同一個(gè)鎖保護(hù)之前所有的修改結(jié)果捉捅。(外連互斥,內(nèi)修可見(jiàn)虽风。)
二. 鎖的使用
Lock也能實(shí)現(xiàn)同步的效果棒口,在使用上更加方便寄月。
1. ReentrantLock類(lèi)? -- Lock lock = new ReentrantLock();
(1) 使用ReentrantLock.lock()獲取鎖(加鎖),線程就擁有了“對(duì)象監(jiān)視器”无牵;
其他線程只有等待ReentrantLock.unlock()釋放鎖(解鎖)漾肮,再次爭(zhēng)搶獲得鎖。
效果和synchronized一致茎毁,但線程執(zhí)行順序是隨機(jī)的克懊。
(2) 關(guān)鍵字與wait()/notify()/notifyAll():實(shí)現(xiàn)等待/通知模式,但是notifyAll()的話七蜘,需要通知所有處于WAITING狀態(tài)的線程谭溉,會(huì)出現(xiàn)相當(dāng)大的效率問(wèn)題。
ReentrantLock和Condition對(duì)象也同樣可以實(shí)現(xiàn)橡卤。在一個(gè)Lock對(duì)象里可以創(chuàng)建多個(gè)Condition(即對(duì)象監(jiān)視器)實(shí)例扮念,可以實(shí)現(xiàn)多路通知功能;實(shí)例對(duì)象可以注冊(cè)在指定的Condition中碧库,從而可以有選擇地進(jìn)行線程通知柜与,在調(diào)度線程上更加靈活。
package lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockService {
?private Lock lock = new ReentrantLock();
?public Condition condition = lock.newCondition();
?public void await(){
??try {
???lock.lock();
???condition.await();//必須在調(diào)用await()之前先調(diào)用lock()以獲得同步監(jiān)視器
??} catch (InterruptedException e) {
???e.printStackTrace();
??}
?}
?
?public void signal(){
??try {
???lock.lock();
???condition.signal();//還有condition.signalAll();
??} finally{
???lock.unlock();
??}??
?}
}
(3) signalAll()
public Condition conditionA = lock.newCondition();
public Condition conditionB = lock.newCondition();
//use:
conditionA.signal(); //or conditionB.signal();
喚醒指定種類(lèi)的線程嵌灰,如conditionA.signal(); 只有用了conditionA的線程被喚醒弄匕。
(4) 公平鎖與非公平鎖
公平鎖:線程獲取鎖的順序是按照線程加鎖的順序來(lái)分配的(FIFO);new ReentrantLock(true);//不一定百分百FIFO沽瞭,但是基本呈有序迁匠。
非公平鎖(默認(rèn)):鎖的搶占機(jī)制,隨機(jī)獲得鎖秕脓。new ReentrantLock(false);
2.相關(guān)方法介紹
(1) int getHoldCount():查詢當(dāng)前線程保持此鎖定的個(gè)數(shù)柒瓣,也就是調(diào)用lock()的次數(shù)。
(2) int getQueueLength():返回正獲取此鎖定的線程估計(jì)數(shù)吠架,如5個(gè)線程芙贫,一個(gè)線程調(diào)用了await(),還有4個(gè)線程在等待鎖的釋放傍药。
(3) int getWaitQueueLength(Condition condition):比如有5個(gè)線程磺平,每個(gè)線程都執(zhí)行了同一個(gè)condition對(duì)象的await(),則結(jié)果為5拐辽。
(4) boolean hasQueuedThread(Thread thread):查詢指定的線程是否正在等待此鎖定拣挪。
(5) boolean hasWaiters(Condition condition):是否有線程正在等待與此鎖定有關(guān)的condition條件。
(6) boolean isFair():判斷是不是公平鎖俱诸。
(7) boolean isHeldByCurrentThread():查詢當(dāng)前線程是否保持此鎖定菠劝。
(8) boolean? isLocked():查詢此鎖定是否由任意線程保持。
(9) void lockInterruptibly():如果當(dāng)前線程未被中斷睁搭,則獲取鎖定赶诊,如果已經(jīng)被中斷則出現(xiàn)異常笼平。
(10) boolean tryLock():僅在調(diào)用時(shí)鎖定未被另一個(gè)線程保持的情況下,才獲取該鎖定舔痪。
(11) boolean tryLock(long timeout,TimeUnit unit):若在給定等待時(shí)間內(nèi)沒(méi)有被另一個(gè)線程保持寓调,且當(dāng)前線程未被中斷,則獲取該鎖定锄码。
(12) Condition.awaitUninterruptibly():在WAITING情況下interrupt()不會(huì)拋出異常夺英。
(13) Condition.awaitUntil(time):線程在等待時(shí)間到達(dá)前,可以被其他線程喚醒滋捶。
3.ReentranReadWriteLock類(lèi)
共享鎖:讀操作相關(guān)的鎖痛悯;排他鎖:寫(xiě)操作相關(guān)的鎖。
讀寫(xiě)重窟,寫(xiě)讀灸蟆,寫(xiě)寫(xiě)都是互斥的;讀讀是異步的亲族,非互斥的。