一草慧、sychronized介紹
????并發(fā)時(shí)桶蛔,多個線程需要操作同一個資源,容易導(dǎo)致錯誤數(shù)據(jù)的產(chǎn)生漫谷,為了解決這個問題仔雷,當(dāng)存在多個線程操作共享數(shù)據(jù)時(shí),需要保證同一時(shí)刻只有一個線程在操作共享數(shù)據(jù)舔示,其他線程必須等到該線程處理完數(shù)據(jù)后再進(jìn)行碟婆。
? ? java中sychronized關(guān)鍵字可以保證同一時(shí)刻只有一個線程可以執(zhí)行某個方法或者代碼塊,同時(shí)sychronized可保證一個線程的變化被其他線程看到(保證可見性)惕稻。
二竖共、sychronized 應(yīng)用方式
? ? 1、作用于實(shí)例方法俺祠,當(dāng)前實(shí)例加鎖肘迎,進(jìn)入同步代碼塊時(shí)需要獲得當(dāng)前實(shí)例的鎖;
? ? 2锻煌、作用于靜態(tài)方法,當(dāng)前類加鎖姻蚓,進(jìn)去同步代碼前需要獲取當(dāng)前類的鎖宋梧;
? ? 3、作用于代碼塊狰挡,這需要指定加鎖對象捂龄,對所給的指定對象加鎖,進(jìn)入同步代碼塊前要獲得指定對象的鎖加叁;
三倦沧、sychronized底層原理
? ? java虛擬機(jī)中的同步Synchronization基于進(jìn)入和退出管程Monitor對象實(shí)現(xiàn)。在java中它匕,sychronized可以修飾同步方法展融,同步方法不是由monitorenter和moniterexit指令來實(shí)現(xiàn)同步,而是由方法調(diào)用指令讀取運(yùn)行時(shí)常量池中的ACC_SYNCHRONIZED標(biāo)注來隱式調(diào)用的豫柬。
?java對象頭與monitor
? ? 在java中告希,對象在內(nèi)存中的布局分為三塊區(qū)域,對象頭烧给,實(shí)例數(shù)據(jù)和填充數(shù)據(jù)燕偶。
1、對象頭
? ? HotSpot虛擬機(jī)的對象頭包括markword和klass础嫡,數(shù)組長度指么;
? ? markword用于存儲對象自身的運(yùn)行時(shí)數(shù)據(jù),如哈希碼(HashCode)、GC分代年齡伯诬、鎖狀態(tài)標(biāo)志晚唇、線程持有的鎖、偏向線程ID姑廉、偏向時(shí)間戳等缺亮,這部分?jǐn)?shù)據(jù)的長度在32位和64位的虛擬機(jī)(未開啟壓縮指針)中分別為32bit和64bit,官方稱它為MarkWord桥言。
? ? 對象頭的另外一部分是klass類型指針萌踱,即對象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過這個指針來確定這個對象是哪個類的實(shí)例号阿。
? ? 數(shù)組長度并鸵,如果對象是一個數(shù)組, 那在對象頭中還必須有一塊數(shù)據(jù)用于記錄數(shù)組長度。
2扔涧、實(shí)例數(shù)據(jù)
? ? 實(shí)例數(shù)據(jù)部分是對象真正存儲的有效信息园担,也是在程序代碼中所定義的各種類型的字段內(nèi)容。無論是從父類繼承下來的枯夜,還是在子類中定義的弯汰,都需要記錄起來。
3湖雹、對象填充
? ??對齊填充并不是必然存在的咏闪,也沒有特別的含義,它僅僅起著占位符的作用摔吏。由于HotSpot VM的自動內(nèi)存管理系統(tǒng)要求對象起始地址必須是8字節(jié)的整數(shù)倍鸽嫂,換句話說,就是對象的大小必須是8字節(jié)的整數(shù)倍征讲。而對象頭部分正好是8字節(jié)的倍數(shù)(1倍或者2倍)据某,因此,當(dāng)對象實(shí)例數(shù)據(jù)部分沒有對齊時(shí)诗箍,就需要通過對齊填充來補(bǔ)全癣籽。
????重量級鎖就是sychronized的對象鎖,鎖標(biāo)識為10滤祖,其中指針指向的是monitor對象的起始地址才避。每個對象都存在著一個monitor與之關(guān)聯(lián),對象與其monitor之間的關(guān)系有存在多重實(shí)現(xiàn)方式氨距,如monitor可以與對象一起創(chuàng)建銷毀或線程試圖獲取對象鎖時(shí)自動生成桑逝,但當(dāng)一個monitor被某個線程持有后,它便處于鎖定狀態(tài)俏让。
? ? 在java虛擬機(jī)中(hotSpot)楞遏,monitor是由ObjectMonitor實(shí)現(xiàn)的茬暇;
? ? ObjectMonitor中有兩個隊(duì)列,_WaitSet和_EntryList寡喝,用來保存ObjectWaiter列表糙俗,每個等待鎖的線程都會封裝成ObjectWaiter對象,_owner指向持有ObjectMonitor對象的線程预鬓,當(dāng)多個線程同時(shí)訪問同一段同步代碼時(shí)巧骚,首先會進(jìn)入_EntryList集合,當(dāng)線程獲取到對象的monitor后進(jìn)入_Owner區(qū)域并把monitor中的_owner變量設(shè)置為當(dāng)前線程格二,同時(shí)monitor的計(jì)數(shù)器_count加1劈彪,若先寫調(diào)用wait()方法,將釋放當(dāng)前持有的monitor,_owner變量恢復(fù)為null顶猜,count自減1沧奴,同時(shí)該線程進(jìn)入_waitSet集合等待被喚醒。若當(dāng)前線程執(zhí)行完畢也將釋放monitor长窄,并復(fù)位變量的值滔吠,以便于其他線程獲取monitor鎖。
? ? monitor對象存在每個java對象的對象頭中(存儲的指針的指向),synchronized鎖便是用過這種方式獲取鎖的挠日,這也是為什么java的任意對象都可以作為鎖的原因疮绷,同時(shí)也是notify/notifyAll/wait方法存在object方法中的原因。
同步方法的實(shí)現(xiàn)原理
同步代碼塊的實(shí)現(xiàn)原理
? ? 同步代碼塊是使用monitorenter和moniterexist指令實(shí)現(xiàn)的嚣潜,會在同步塊的區(qū)域通過監(jiān)聽器對象去獲取鎖和釋放鎖冬骚。
? ? 同步方法和靜態(tài)同步方法是依賴在方法修飾符ACC_SYNCHRONIZED實(shí)現(xiàn),當(dāng)方法調(diào)用時(shí)郑原,調(diào)用指令將會檢查方法的ACC_SYNCHRONIZED訪問標(biāo)志是否被設(shè)置,如果設(shè)置了夜涕,執(zhí)行時(shí)將先獲取monitor犯犁,獲取成功夠才能執(zhí)行方法體,方法執(zhí)行完成后再釋放monitor女器,在方法執(zhí)行期間酸役,其他任何線程都無法在獲得同一個monitor對象。
四驾胆、多線程面試
1涣澡、雙重校驗(yàn)鎖的單例模式
? ? volatile修飾singleton很有必要,voliatile防止了指令重排丧诺,singleton =new Singleton();其實(shí)分為了三步:
? ? 1.為singleton分配空間入桂;2.初始化singleton;3.將singleton指向分配的內(nèi)存地址驳阎;
? ? 由于jvm有指令重排的特性抗愁,執(zhí)行順序可能變成1->3->2馁蒂,指令重排在單線程下可能沒有問題,但是在多線程下可能會導(dǎo)致一個線程獲得還沒有初始化的實(shí)例蜘腌。
2沫屡、JDK1.6后的sychronized關(guān)鍵字底層做了哪些優(yōu)化?
? ? 引入了偏向鎖撮珠、輕量級鎖沮脖、自旋鎖、適應(yīng)性自旋鎖芯急、鎖消除勺届、鎖粗化等技術(shù)減少所操作的開銷。
? ? 鎖主要存在四種狀態(tài)志于,無鎖狀態(tài)涮因,偏向鎖狀態(tài),輕量級鎖狀態(tài)伺绽,重量級鎖狀態(tài)养泡,鎖可以升級不能降級,這種策略是為了提高獲得鎖和釋放鎖的效率奈应。
3澜掩、sychronized和ReenTrantLock的區(qū)別
? ? 兩者都是可重入鎖:自己可以再次獲取自己的內(nèi)部鎖,此時(shí)鎖的計(jì)數(shù)器count加1就行杖挣,多以要等到鎖的計(jì)數(shù)器降為0才能釋放鎖肩榕。
? ? sychronized依賴于jvm底層,ReenTrantLock依賴于API惩妇。
? ? ReenTrantLock比sychronized增加了一些高級功能:等待可中斷株汉,可實(shí)現(xiàn)公平鎖,可實(shí)現(xiàn)選擇性通知:
? ? (1)ReenTrantLock使用lock.lockInterruptibly()來實(shí)現(xiàn)等待可中斷功能歌殃,也就是說正在等待的線程可以選擇放棄等待乔妈;
? ? (2)ReenTrantLock 可以指定公平鎖還是非公平鎖,而sychronized只能是非公平鎖氓皱,公平鎖就是先等待的線程先獲得鎖路召;實(shí)現(xiàn)方式是在ReentrantLock(boolean fair)構(gòu)造方法來制定是否是公平的。
? ? (3)sychronized和wait(),notify(),notifyAll()方法結(jié)合可以實(shí)現(xiàn)等待通知機(jī)制波材,ReenTrantLock類也可以實(shí)現(xiàn)股淡,需要使用Condition接口和newCondition()方法。Condition具有很好的靈活性廷区,可以在一個Lock對象中可以創(chuàng)建多個Condition實(shí)例(對象監(jiān)視器),線程對象可以注冊在指定的Condition中唯灵,從而可以選擇性的進(jìn)行線程通知,在調(diào)度線程上更加靈活隙轻。在使用notify()/notifyAll()方法進(jìn)行通知時(shí)早敬,被通知的線程是由JVM選擇的忌傻,用ReenTrantLock類結(jié)合Condition實(shí)例可以實(shí)現(xiàn)選擇性通知。而sychronized關(guān)鍵字就相當(dāng)于整個Lock對象中只有一個Condition搞监,多有的線程都注冊在它一個身上水孩。如果執(zhí)行notifyAll()放的話就會通知所有處于等待狀態(tài)的線程,而Condition的singalAll()方法只會喚醒注冊在該Condition實(shí)例中的所有等待對象琐驴。
? ? 性能上已經(jīng)不是選擇標(biāo)準(zhǔn):在高并發(fā)下俘种,ReenTrantLock的效率比sychronized的效率高一些;
3绝淡、volatile關(guān)鍵字
? ? 在現(xiàn)在的java模型下宙刘,線程可以把變量保存在本地內(nèi)存(比如寄存器)中,而不是直接在主內(nèi)存中進(jìn)行讀寫牢酵,這就可能造成一個線程在主內(nèi)存中修改了一個變量的值悬包,而另外一個線程還繼續(xù)使用它在寄存器中的變量的拷貝,造成數(shù)據(jù)不一致馍乙。
? ? 這個問題可以用volatile關(guān)鍵字解決布近,將變量聲明為volatile,這就指示JVM這個變量不穩(wěn)定丝格,每次使用它都去主內(nèi)存讀取撑瞧。
? ? volatile關(guān)鍵字就是保證變量的可見性和防止指令的重排序。
4显蝌、sychronized和volatile的區(qū)別
? ? sychronized關(guān)鍵字可以修飾方法预伺,變量和代碼塊定拟,而volatile只能修飾變量首启;
????volatile是sychronized的輕量級實(shí)現(xiàn)尘分,所以性能比sychronized好咕村;但是JDK1.6后優(yōu)化了鎖,sychronized性能得到了提升懒鉴,在實(shí)際任務(wù)中使用sychronized的時(shí)候更加多一些徘跪。
? ? 多線程訪問volatile變量不會發(fā)生阻塞讳推,而訪問sychronized關(guān)鍵字會方式阻塞艾船。
? ? volatile關(guān)鍵字能保證數(shù)據(jù)的可見性葵腹,但是不能保證數(shù)據(jù)的原子性高每,sychronized保證了可見性和原子性屿岂。
? ? volatile主要保證變量在線程間的可見性,而sychronized主要保證數(shù)據(jù)在多線程間的的同步性鲸匿。
5爷怀、線程池
? ? 線程池提供了一種限制和管理資源,每個線程池還維護(hù)了一些基本統(tǒng)計(jì)信息带欢,比如線程的數(shù)量运授。
? ? 線程池的優(yōu)點(diǎn):
? ? 降低資源消耗烤惊,通過重復(fù)利用已創(chuàng)建的線程降低線程的創(chuàng)建和銷毀的消耗。
? ? 提高響應(yīng)速度:當(dāng)任務(wù)到達(dá)時(shí)吁朦,不需要等到創(chuàng)建線程就可以立即執(zhí)行柒室。
? ? 提高線程的可管理性,線程是稀缺資源逗宜,如果無限制的創(chuàng)建雄右,不僅會消耗系統(tǒng)資源,還會降低系統(tǒng)的穩(wěn)定性纺讲,使用線程池可以進(jìn)行統(tǒng)一的分配擂仍,調(diào)優(yōu)和監(jiān)控。
6熬甚、實(shí)現(xiàn)Runnable和Callable接口的區(qū)別
? ? 如果想讓線程池執(zhí)行任務(wù)的話需要實(shí)現(xiàn)Runnable或者Callable接口逢渔,兩個接口都可以被ThreadPoolExcutor和ScheduledPoolExcutor執(zhí)行。但是Runnable接口不會返回結(jié)果乡括,而Callable接口可以返回結(jié)果肃廓。
????工具類Executors可以實(shí)現(xiàn)Runnable對象和Callable對象之間的相互轉(zhuǎn)換。(Executors.callable(Runnable task)或Executors.callable(Runnable task粟判,Object resule))亿昏。
7、執(zhí)行execute()方法和submit()方法的區(qū)別
? ? execute()方法用于提交不需要返回值的任務(wù)档礁,所以午飯判斷任務(wù)是否被線程池執(zhí)行成功與否角钩;
? ? submit()方法用于提交需要返回值的任務(wù),線程池會返回一個future類型的對象呻澜,通過這個對象來判斷任務(wù)是否執(zhí)行成功递礼。并且可以通過future的get()方法來獲取返回值,get()方法會阻塞當(dāng)前線程直到任務(wù)完成羹幸,而使用get(long timeout脊髓,TimeUnit unit)方法則會阻塞當(dāng)前線程一段時(shí)間后立即返回,這時(shí)候有可能任務(wù)沒有執(zhí)行完栅受。
8将硝、創(chuàng)建線程池
? ? 一般不建議使用Excutors去創(chuàng)建,而是通過ThreadPoolExcutor的方式屏镊,這樣的處理方式更加明確線程池的運(yùn)行規(guī)則依疼,規(guī)避資源耗盡的風(fēng)險(xiǎn)。
一般通過構(gòu)造方法可以創(chuàng)建
使用Excutors返回線程池對象存在問題:
FixThreadPool和SingleThreadExutor:允許請求的隊(duì)列長度為Integer.MAX_VALUE而芥,可能堆積的請求過多律罢,導(dǎo)致OOM,
CachedThreadPool和ScheduledThreadPool:允許創(chuàng)建的現(xiàn)場數(shù)量為Integer.MAX_VALUE,可能導(dǎo)致大量的線程被創(chuàng)建棍丐,導(dǎo)致OOM误辑。
通過Executor的工具類Excutors實(shí)現(xiàn):
9沧踏、Atomic原子類
? ? Atomic是指一個操作不可中斷,一旦開始巾钉,就不會被其他線程干擾翘狱。Atomic原子類就是具有原子/原子操作特性的類。
? ? 并發(fā)包java.util.concurrent的原子類都存放在java.util.concurrent.atomic下:
? ? 主要分為四類:
? ? 基本類型:AtomicBoolean砰苍,AtomicInteger盒蟆,AtomicLong
? ? 數(shù)組類型:AtomicIntegerArray,AtomicLongArray师骗,AtomicReferenceArray
? ? 引用類型:AtomicReference历等,AtomicStampedRerence,AtomicMarkableReference?
? ? 對象屬性修改類型:AtomicIntegerFieldUpdater辟癌,AtomicLongFieldUpdater寒屯,AtomicLongFieldUpdater
? ? AtomicInteger的方法:
? ? AtomicInteger類主要是利用CAS +volatile和native方法來保證原子操作,從而避免sychronized的高開銷黍少,執(zhí)行效率大為提升寡夹。
????CAS的原理是拿期望的值和原本的一個值作比較,如果相同則更新成新的值厂置。UnSafe 類的 objectFieldOffset() 方法是一個本地方法菩掏,這個方法是用來拿到“原來的值”的內(nèi)存地址,返回值是 valueOffset昵济。另外 value 是一個volatile變量智绸,在內(nèi)存中可見,因此 JVM 可以保證任何時(shí)刻任何線程總能拿到該變量的最新值访忿。
10瞧栗、使用CAS實(shí)現(xiàn)單例模式
? ? 這個實(shí)現(xiàn)里面有一個循環(huán),是基于忙等待的算法海铆,依賴底層硬件的實(shí)現(xiàn)迹恐,相當(dāng)于鎖它沒有線程切換和阻塞額外消耗,可以支持較大的并行度卧斟。
? ? CAS的一個重要缺點(diǎn)是殴边,如果忙等待一直執(zhí)行不成功,會對CPU造成較大的執(zhí)行開銷珍语。如果多個線程同時(shí)執(zhí)行到singleton = new Singleton()的時(shí)候锤岸,會創(chuàng)建大量的Singleton對象,很可能會造成內(nèi)存溢出廊酣,所以不建議使用這種能耻。
11赏枚、AQS
????AQS是一個用來構(gòu)建鎖和同步器的框架亡驰,使用AQS能簡單且高效地構(gòu)造出應(yīng)用廣泛的大量的同步器晓猛,比如我們提到的ReentrantLock,Semaphore凡辱,其他的諸如ReentrantReadWriteLock戒职,SynchronousQueue,F(xiàn)utureTask等等皆是基于AQS的透乾。當(dāng)然洪燥,我們自己也能利用AQS非常輕松容易地構(gòu)造出符合我們自己需求的同步器。
12乳乌、happen-before規(guī)則
? ? 有些指令是可以重排的捧韵,有些指令是不可以重排的。規(guī)則如下:
? ? 程序順序規(guī)則:一個線程內(nèi)保證語義的串行性汉操,比如第二條語句依賴第一條語句執(zhí)行的結(jié)果再来,那么就不能執(zhí)行第二條再執(zhí)行第一條。
? ? volatile原則:volatile防止了指令重排磷瘤,volatile變量的寫先于讀芒篷,這保證了volatile變量的可見性。
? ? 鎖規(guī)則:先解鎖采缚,后續(xù)步驟再加鎖针炉,加鎖不能重排到解鎖之前,這樣加鎖行為無法獲得多扳抽。
? ? 傳遞性:A先于B篡帕,B先于C,則A先于C贸呢。
? ? 線程的start()先于它的每個動作赂苗。
? ? 線程的所有操作先于線程的終結(jié)。
? ? 線程的中斷先于中斷線程的代碼贮尉。
? ? 對象的構(gòu)造函數(shù)執(zhí)行拌滋、結(jié)束先于finalize()方法。
13猜谚、ReadWritLock
(1)Java并發(fā)庫中ReetrantReadWriteLock實(shí)現(xiàn)了ReadWriteLock接口并添加了可重入的特性
(2)ReetrantReadWriteLock讀寫鎖的效率明顯高于synchronized關(guān)鍵字
(3)ReetrantReadWriteLock讀寫鎖的實(shí)現(xiàn)中败砂,讀鎖使用共享模式;寫鎖使用獨(dú)占模式魏铅,換句話說昌犹,讀鎖可以在沒有寫鎖的時(shí)候被多個線程同時(shí)持有,寫鎖是獨(dú)占的
(4)ReetrantReadWriteLock讀寫鎖的實(shí)現(xiàn)中览芳,需要注意的斜姥,當(dāng)有讀鎖時(shí),寫鎖就不能獲得;而當(dāng)有寫鎖時(shí)铸敏,除了獲得寫鎖的這個線程可以獲得讀鎖外缚忧,其他線程不能獲得讀鎖
14、線程池的處理流程
(1)判斷線程池里的核心線程是否都在執(zhí)行任務(wù)杈笔,如果有核心線程空閑或者核心線程還沒有創(chuàng)建闪水,則創(chuàng)建一個新的工作線程來執(zhí)行任務(wù);如果核心線程都在執(zhí)行任務(wù)蒙具,則進(jìn)入下個流程球榆。
(2)線程判斷工作隊(duì)列是否已滿,如果工作隊(duì)列沒有滿禁筏,則將新提交的任務(wù)存儲在這個工作隊(duì)列里持钉。如果工作隊(duì)列滿了,則進(jìn)入下一個流程篱昔。
(3)判斷線程池里的線程是否都處于工作狀態(tài)右钾,如果沒有,則創(chuàng)建一個新的工作線程來執(zhí)行任務(wù)旱爆,如果滿了舀射,則交給飽和策略來處理這個任務(wù)。