Java面试参考指南——同步
同步
在多線程程序中,同步修飾符用來控制對(duì)臨界區(qū)代碼的訪問。其中一種方式是用synchronized關(guān)鍵字來保證代碼的線程安全性。在Java中,synchronized修飾的代碼塊或方法不會(huì)被多個(gè)線程并發(fā)訪問。它強(qiáng)制要求線程在進(jìn)入一個(gè)方法之前獲得一個(gè)鎖,在離開方法時(shí)釋放該鎖。它保證了在同一時(shí)刻只有一個(gè)線程能執(zhí)行被其修飾的方法。
如果我們把一個(gè)方法或代碼塊定義為同步的,就意味著在同一個(gè)對(duì)象中,只會(huì)有一個(gè)對(duì)同步方法的調(diào)用。如果在一個(gè)線程內(nèi)部調(diào)用了一個(gè)同步方法,則其他線程會(huì)一直阻塞,直到第一個(gè)線程完成方法調(diào)用。
在進(jìn)入一個(gè)對(duì)象的同步方法之前,需要申請(qǐng)對(duì)該對(duì)象上鎖,完成方法調(diào)用后釋放鎖供其他線程申請(qǐng)。同步方法遵循happens-before機(jī)制,它保證了對(duì)象狀態(tài)的改變?cè)谄渌€程中都是可見的。
當(dāng)標(biāo)記一個(gè)代碼塊為同步時(shí),需要用一個(gè)對(duì)象作為參數(shù)。當(dāng)一個(gè)運(yùn)行線程執(zhí)行到該代碼塊時(shí),要等到其他運(yùn)行線程退出這個(gè)對(duì)象的同步代碼區(qū)。然而,一個(gè)線程可以進(jìn)入另一個(gè)對(duì)象的同步代碼區(qū)。但是同一個(gè)對(duì)象的非同步方法可以不用申請(qǐng)鎖。
如果定義一個(gè)靜態(tài)方法為同步,則是在類上同步,而不是在對(duì)象上同步。也即如果一個(gè)靜態(tài)同步方法在執(zhí)行時(shí),整個(gè)類被鎖住,對(duì)該類中的其他靜態(tài)方法調(diào)用會(huì)阻塞。
1)當(dāng)一個(gè)線程進(jìn)入了一個(gè)實(shí)例的同步方法,則其他任何線程都不能進(jìn)入該實(shí)例的任何一個(gè)同步方法。
2)當(dāng)一個(gè)線程進(jìn)入了一個(gè)類的靜態(tài)同步方法,則其他任何線程都不能進(jìn)入該類的任何一個(gè)靜態(tài)同步方法。
注意:
監(jiān)視器或內(nèi)部鎖
鎖限制了對(duì)某個(gè)對(duì)象狀態(tài)的訪問,同時(shí)保證了happens-before關(guān)系。
每個(gè)對(duì)象都有一個(gè)鎖對(duì)象,一個(gè)線程在訪問對(duì)象之前必須申請(qǐng)鎖,完成以后釋放鎖。其他線程不能訪問對(duì)象,知道獲得該對(duì)象的鎖。這保證了一個(gè)線程改變了對(duì)象的狀態(tài)后,新的狀態(tài)對(duì)其他在同一個(gè)監(jiān)視器上線程可見。
當(dāng)線程釋放鎖時(shí),會(huì)將cache中的內(nèi)容更新到主內(nèi)存,這也就使得該對(duì)象的狀態(tài)變化對(duì)其他線程是可見的——這就是happens-before關(guān)系。
synchronized和volatile,包括Thread.start()和Thread.join()方法,都能保證happens-before關(guān)系。
同步語句和同步方法獲取的鎖相同,某個(gè)線程可以請(qǐng)求同一個(gè)鎖多次。
一個(gè)線程獲得了對(duì)象鎖后,不會(huì)影響其他線程訪問對(duì)象的字段或調(diào)用對(duì)象的非同步方法。
同步語句首先嘗試獲取對(duì)象的鎖,獲取成功后立即開始執(zhí)行同步代碼塊,執(zhí)行完后釋放鎖。
如果方法是對(duì)象成員或?qū)ο髮?shí)例,線程將鎖住該實(shí)例。如果方法是靜態(tài)的,線程鎖住的是該類對(duì)應(yīng)的Class對(duì)象。同步方法用SYNCHRONIZED標(biāo)記,該標(biāo)記被方法調(diào)用指令識(shí)別。
原子變量
來看語句?int c++,它包含多個(gè)操作,e.g. 從內(nèi)存讀取c的值,將c的值加1,然后寫回內(nèi)存。這個(gè)操作對(duì)單線程來說是正確的,但是在多線程環(huán)境卻可能出錯(cuò)。它存在競(jìng)態(tài)條件,在多線程環(huán)境中可能多個(gè)線程同時(shí)讀取c的值
原子訪問保證所有操作作為一個(gè)整體一次完成。一個(gè)原子操作要么完全執(zhí)行要么完全不執(zhí)行。
以下這些操作能認(rèn)為是原子操作:
Java并發(fā)包java.util.concurrent.atomic定 義了對(duì)單個(gè)變量進(jìn)行原子操作的類。所有類都有g(shù)et和set方法,就像對(duì)volatile變量的讀寫一樣。這就意味著,一個(gè)寫操作happens- before其他任何對(duì)該變量的讀操作。原子方法compareAndSet同樣有這些特性,就像對(duì)整型變量做原子的算術(shù)運(yùn)算一樣。
在Java 5.0的并發(fā)包中,定義了支持原子操作的類。Java虛擬機(jī)編譯這些類時(shí)利用硬件提供的CAS(Compare and set)來實(shí)現(xiàn)。
- AtomicInteger
- AtomicLong
- AtomicBoolean
- AtomicReference
volatile變量
volatile只能用來修飾變量。用volatile修飾的變量可能被異步地修改,所以編譯器會(huì)對(duì)它們特殊處理。
volatile修飾符保證讀取某個(gè)字段的任何線程都能看到該變量最近被寫入的值。
使用volatile修飾的變量降低了內(nèi)存一致性的風(fēng)險(xiǎn),因?yàn)槿魏螌?duì)volatile變量的寫操作都能被其他線程可見。另外,當(dāng)一個(gè)線程訪問volatile變量時(shí),不止能看到對(duì)該變量最近的修改,還能修改該變量的代碼所帶來的其他影響。
在多線程環(huán)境中,對(duì)象在不同線程中都保存有副本。但是volatile變量卻沒有,它們?cè)诙阎兄挥幸粋€(gè)實(shí)例。這樣對(duì)volatile變量的修改就能立即對(duì)其他線程可見。另外,本地線程緩存沒有完成后刷新的工作。
volatile能夠保證可見性,但是也帶來了競(jìng)態(tài)條件。它不會(huì)鎖定等待完成某個(gè)操作。例如:
| 1 | volatile int i=0; |
兩 個(gè)線程同時(shí)執(zhí)行?i +=5?時(shí),會(huì)得到5-10之間的某個(gè)值(譯者注:原文為i +=5 invoking by two simultaneously thread give result 5 or 10 but it guarantee to see immediate changes 感覺有問題)
使用場(chǎng)景:用一個(gè)volatile布爾變量作為一個(gè)線程終止的標(biāo)志。
靜態(tài)和volatile變量之間的差別
聲明一個(gè)靜態(tài)變量,意味著該類的多個(gè)實(shí)例將共享該變量,靜態(tài)變量與類關(guān)聯(lián)而不是與對(duì)象關(guān)聯(lián)。線程可能會(huì)有靜態(tài)變量的本地緩存值。
當(dāng)兩個(gè)線程同時(shí)更新靜態(tài)(非volatile)變量的值時(shí),可能有一個(gè)線程的緩存中是一個(gè)過期的值。雖然多線程能夠訪問的是同一個(gè)靜態(tài)變量,每個(gè)線程還是可能會(huì)保存自己的緩存副本。
一個(gè)volatile變量則在內(nèi)存中只保留一個(gè)副本,該副本在多個(gè)線程中共享。
volatile變量和同步之間的差別
在線程內(nèi)存和主內(nèi)存之間,volatile只是同步了一個(gè)變量的值,synchronized則同步了(synchronized塊中)所有變量的值,并且會(huì)鎖住和釋放一個(gè)監(jiān)視器。所以,synchronized比volatile會(huì)有更多的開銷。
volatile變量不允許有一個(gè)本地副本與主內(nèi)存中的值不同。一個(gè)聲明為volatile的變量必須保證所有線程中的副本同步,不管哪個(gè)線程修改了變量的值,另外其他線程都能立即看到該值。
鎖對(duì)象
鎖對(duì)象的作用像synchronized代碼使用的隱式鎖一樣。像隱式鎖一樣,同時(shí)只能有一個(gè)線程持有鎖。鎖還支持wait/notify機(jī)制,通過他們之間的condition對(duì)象。
鎖對(duì)象相對(duì)于隱式鎖最大的優(yōu)點(diǎn)是,他們能從嘗試獲得鎖的狀態(tài)返回。如果鎖當(dāng)前不可用或者在一個(gè)超時(shí)時(shí)間之前,tryLock()方法能夠返回。在獲得鎖之前,如果其他線程發(fā)送了一個(gè)中斷,lockInterruptibly()方法能返回。
Java內(nèi)存回收
在 Java中,創(chuàng)建的對(duì)象存放在堆中。Java堆被稱為內(nèi)存回收堆。內(nèi)存收集不能強(qiáng)制執(zhí)行。當(dāng)內(nèi)存收集器運(yùn)行時(shí),它釋放掉那些不可達(dá)對(duì)象占用的內(nèi)存。垃圾收集線程作為一個(gè)優(yōu)先級(jí)較低的守護(hù)線程運(yùn)行。你能通過System.gc()提示虛擬機(jī)進(jìn)行垃圾回收,但是不能強(qiáng)迫其執(zhí)行。
如何寫一個(gè)死鎖程序
在多線程環(huán)境中,死鎖意味著兩個(gè)或多個(gè)線程一直阻塞,等待其他線程釋放鎖。下面是死鎖的一個(gè)示例:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | public class DeadlockSample { ????private final Object obj1 = new Object(); ????private final Object obj2 = new Object(); ????public static void main(String[] args) { ????????DeadlockSample test = new DeadlockSample(); ????????test.testDeadlock(); ????} ????private void testDeadlock() { ????????Thread t1 = new Thread(new Runnable() { ????????????public void run() { ????????????????calLock12(); ????????????} ????????}); ????????Thread t2 = new Thread(new Runnable() { ????????????public void run() { ????????????????calLock21(); ????????????} ????????}); ????????t1.start(); ????????t2.start(); ????} ????private void calLock12() { ????????synchronized (obj1) { ????????????sleep(); ????????????synchronized (obj2) { ????????????????sleep(); ????????????} ????????} ????} ????private void calLock21() { ????????synchronized (obj2) { ????????????sleep(); ????????????synchronized (obj1) { ????????????????sleep(); ????????????} ????????} ????} ????private void sleep() { ????????try { ????????????Thread.sleep(100); ????????} catch (InterruptedException e) { ????????????e.printStackTrace(); ????????} ????} } |
Java中的引用類型
java.lang.ref包能用來聲明軟引用(soft reference),弱引用(weak reference)和虛引用(phantom reference)。
- 垃圾收集器不會(huì)回收強(qiáng)引用。
- 在內(nèi)存不足時(shí)才會(huì)回收軟引用,所以用它實(shí)現(xiàn)緩存可以避免內(nèi)存不足。
- 垃圾收集器將會(huì)在下一次垃圾收集時(shí)回收弱引用。弱引用能被用來實(shí)現(xiàn)特殊的map。java.util.WeakHashMap中的key就是弱引用。
- 虛引用會(huì)被立即回收。能被用來跟蹤對(duì)象被垃圾回收的活動(dòng)。
譯文鏈接:?http://www.importnew.com/13159.html
本文由?ImportNew?-?秋雙?翻譯自?techmytalk。
總結(jié)
以上是生活随笔為你收集整理的Java面试参考指南——同步的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据库连接池浅析
- 下一篇: 在Java中如何高效的判断数组中是否包含