九種基本類型及封裝類
基本類型booleanbytecharshortintlongdoublefloat
二進(jìn)制位數(shù)18(一字節(jié))16(2字節(jié))16(2字節(jié))32(4字節(jié))64(8字節(jié))64(8字節(jié))32(4字節(jié))
封裝器類BooleanByteCharacterShortIntegerLongDoubleVoid
switch語句后的控制表達(dá)式只能是short亏较、char汗贫、int淌喻、long整數(shù)類型和枚舉類型片部,不能是float籽懦,double和boolean類型财骨。String類型是java7開始支持肋拔。
位運算符
左移(<<)
右移(>>):int是32位锈津,最高位是符號位,0代表正數(shù)凉蜂,1代表負(fù)數(shù)琼梆,負(fù)數(shù)以補碼的形式存儲在計算機中。右移規(guī)則:最高位是什么(0或者1)窿吩,右移的時候左邊就補什么茎杂。即正數(shù)右移用0補位左邊,負(fù)數(shù)右移用1補位左邊纫雁。
無符號右移(>>>):不管是負(fù)數(shù)還是正數(shù)煌往,右移總是左邊補0。
與運算(&)
或運算(|)
非運算(~)
異或運算(^):位相同為0轧邪,相異為1
-5右移3位后結(jié)果為-1刽脖,-1的二進(jìn)制為:11111111111111111111111111111111// (用1進(jìn)行補位)-5無符號右移3位后的結(jié)果536870911換算成二進(jìn)制:00011111111111111111111111111111// (用0進(jìn)行補位)
應(yīng)用:不用臨時變量交換兩個數(shù)
voidswap(intargc,char*argv[]){? ? a = a ^ b;? ? b = b ^ a;? ? a = a ^ b;}
for循環(huán),F(xiàn)orEach忌愚,迭代器效率
直接for循環(huán)效率最高曾棕,其次是迭代器和 ForEach操作。
其實ForEach 編譯成字節(jié)碼之后菜循,使用的是迭代器實現(xiàn)的翘地。
synchronized和volatile
volatile僅能使用在變量級別;synchronized則可以使用在變量癌幕、方法衙耕、和類級別的。
volatile保證了變量的可見性勺远,synchronized保證了原子性和可見性橙喘。
volatile
原理:首先我們要先意識到有這樣的現(xiàn)象,編譯器為了加快程序運行的速度胶逢,對一些變量的寫操作會先在寄存器或者是CPU緩存上進(jìn)行厅瞎,最后才寫入內(nèi)存饰潜。而在這個過程,變量的新值對其他線程是不可見的,而volatile的作用就是使它修飾的變量的讀寫操作都必須在內(nèi)存中進(jìn)行和簸。volatile告訴JVM彭雾, 它所修飾的變量不保留拷貝,直接訪問主內(nèi)存中的锁保。
volatile與synchronized
volatile本質(zhì)是在告訴JVM當(dāng)前變量在寄存器中的值是不確定的薯酝,需要從主存中讀取,synchronized則是鎖定當(dāng)前變量爽柒,只有當(dāng)前線程可以訪問該變量吴菠,其他線程被阻塞住。
volatile僅能使用在變量級別浩村,synchronized則可以使用在變量做葵,方法.
volatile僅能實現(xiàn)變量的修改可見性,但不具備原子特性心墅,而synchronized則可以保證變量的修改可見性和原子性酿矢。
volatile不會造成線程的阻塞,而synchronized可能會造成線程的阻塞嗓化。
volatile標(biāo)記的變量不會被編譯器優(yōu)化棠涮,而synchronized標(biāo)記的變量可以被編譯器優(yōu)化谬哀。
volatile不能保證原子性原因:線程A修改了變量還沒結(jié)束時刺覆,另外的線程B可以看到已修改的值,而且可以修改這個變量,而不用等待A釋放鎖史煎,因為Volatile 變量沒上鎖谦屑。
注意
聲明為volatile的簡單變量如果當(dāng)前值由該變量以前的值相關(guān),那么volatile關(guān)鍵字不起作用篇梭。
也就是說如下的表達(dá)式都不是原子操作: n? =? n? +1; n ++ ;
只有當(dāng)變量的值和自身上一個值無關(guān)時對該變量的操作才是原子級別的氢橙,如n = m + 1。
Java內(nèi)存模型的抽象(volatile)
在java中恬偷,所有實例域悍手、靜態(tài)域和數(shù)組元素存儲在堆內(nèi)存中,堆內(nèi)存在線程之間共享(本文使用“共享變量”這個術(shù)語代指實例域袍患,靜態(tài)域和數(shù)組元素)坦康。局部變量,方法定義參數(shù)和異常處理器參數(shù)不會在線程之間共享诡延,在棧內(nèi)存中滞欠,不需要同步處理,因為棧內(nèi)存是線程獨享的肆良,它們不會有內(nèi)存可見性問題筛璧,也不受內(nèi)存模型的影響逸绎。
Java線程之間的通信由Java內(nèi)存模型(本文簡稱為JMM)控制,JMM決定一個線程對共享變量的寫入何時對另一個線程可見夭谤。從抽象的角度來看棺牧,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲在主內(nèi)存(main memory)中,每個線程都有一個私有的本地內(nèi)存(local memory)沮翔,本地內(nèi)存中存儲了該線程以讀/寫共享變量的副本(寄存器或CPU緩存)本地內(nèi)存是JMM的一個抽象概念陨帆,并不真實存在。它涵蓋了緩存采蚀,寫緩沖區(qū)疲牵,寄存器以及其他的硬件和編譯器優(yōu)化。Java內(nèi)存模型的抽象示意圖如下:
java內(nèi)存模型
從上圖來看榆鼠,線程A與線程B之間如要通信的話纲爸,必須要經(jīng)歷下面2個步驟:
首先,線程A把本地內(nèi)存A中更新過的共享變量刷新到主內(nèi)存中去妆够。
然后识啦,線程B到主內(nèi)存中去讀取線程A之前已更新過的共享變量。
下面通過示意圖來說明這兩個步驟:
如上圖所示神妹,本地內(nèi)存A和B有主內(nèi)存中共享變量x的副本颓哮。假設(shè)初始時,這三個內(nèi)存中的x值都為0鸵荠。線程A在執(zhí)行時冕茅,把更新后的x值(假設(shè)值為1)臨時存放在自己的本地內(nèi)存A中。當(dāng)線程A和線程B需要通信時蛹找,線程A首先會把自己本地內(nèi)存中修改后的x值刷新到主內(nèi)存中姨伤,此時主內(nèi)存中的x值變?yōu)榱?。隨后庸疾,線程B到主內(nèi)存中去讀取線程A更新后的x值乍楚,此時線程B的本地內(nèi)存的x值也變?yōu)榱恕?/p>
從整體來看,這兩個步驟實質(zhì)上是線程A在向線程B發(fā)送消息届慈,而且這個通信過程必須要經(jīng)過主內(nèi)存徒溪。JMM通過控制主內(nèi)存與每個線程的本地內(nèi)存之間的交互,來為java程序員提供內(nèi)存可見性保證金顿。
equals與==的區(qū)別
==常用于比較原生類型臊泌,而equals()方法用于檢查對象的相等性。另一個不同的點是:如果==和equals()用于比較對象串绩,當(dāng)兩個引用地址相同缺虐,== 返回true。而equals()可以返回true或者false主要取決于重寫實現(xiàn)礁凡。最常見的一個例子高氮,字符串的比較慧妄,不同情況==和equals()返回不同的結(jié)果。
看Object源碼:
publicbooleanequals(Object obj){return(this== obj);? ? }
==表示的是比較兩個對象實例的內(nèi)存地址是否相同剪芍。如果不重寫equal()塞淹,就和==等效,
相等(相同)的對象必須具有相等的哈希碼(或者散列碼)罪裹。
如果兩個對象的hashCode相同饱普,它們并不一定相同。
術(shù)語來講的區(qū)別:
==是判斷兩個變量或?qū)嵗遣皇侵赶蛲粋€內(nèi)存空間状共。
equals是判斷兩個變量或?qū)嵗赶虻膬?nèi)存空間的值是不是相同套耕。
==指引用是否相同
equals()指的是值是否相同
hashCode作用
以java.lang.Object來理解JVM每new一個Object,它都會將這個Object丟到一個Hash哈希表中去峡继,這樣的話冯袍,下次做Object的比較或者取這個對象的時候,它會根據(jù)對象的hashcode再從Hash表中取這個對象碾牌。這樣做的目的是提高取對象的效率康愤。
具體過程是這樣:
new Object(),JVM根據(jù)這個對象的Hashcode值放入到對應(yīng)的Hash表對應(yīng)的Key上舶吗,如果不同的對象卻產(chǎn)生了相同的hash值征冷,也就是發(fā)生了Hash key相同導(dǎo)致沖突的情況,那么就在這個Hash key的地方產(chǎn)生一個鏈表誓琼,將所有產(chǎn)生相同hashcode的對象放到這個單鏈表上串在一起检激。
比較兩個對象的時候,首先根據(jù)他們的hashcode去hash表中找他的對象踊赠,當(dāng)兩個對象的hashcode相同呵扛,那么就是說他們這兩個對象放在Hash表中的同一個key上每庆,那么他們一定在這個key上的鏈表上筐带。那么此時就只能根據(jù)Object的equal方法來比較這個對象是否equal。當(dāng)兩個對象的hashcode不同的話缤灵,肯定他們不能equal伦籍。
java.lang.Object中對hashCode的約定:
在一個應(yīng)用程序執(zhí)行期間,如果一個對象的equals方法做比較所用到的信息沒有被修改的話腮出,則對該對象調(diào)用hashCode方法多次帖鸦,它必須始終如一地返回同一個整數(shù)。
如果兩個對象根據(jù)equals(Object o)方法是相等的胚嘲,則調(diào)用這兩個對象中任一對象的hashCode方法必須產(chǎn)生相同的整數(shù)結(jié)果作儿。
如果兩個對象根據(jù)equals(Object o)方法是不相等的,則調(diào)用這兩個對象中任一個對象的hashCode方法馋劈,不要求產(chǎn)生不同的整數(shù)結(jié)果攻锰。但如果能不同晾嘶,則可能提高散列表的性能。
Object的公用方法
clone保護方法娶吞,只有實現(xiàn)了Cloneable接口才可以調(diào)用垒迂,否則拋異常
getClassfinal方法,獲得運行時類型
toString
equals
hashCode
wait就是使當(dāng)前線程等待該對象的鎖妒蛇,當(dāng)前線程必須是該對象的擁有者机断,也就是具有該對象的鎖。wait方法一直等待绣夺,直到獲得鎖或者被中斷吏奸。wait設(shè)定一個超時間隔,如果在規(guī)定時間內(nèi)沒有獲得鎖就返回陶耍。
調(diào)用該方法后當(dāng)前線程進(jìn)入睡眠狀態(tài)苦丁,直到以下事件發(fā)生。
其他線程調(diào)用了該對象的notify方法物臂。
其他線程調(diào)用了該對象的notifyAll方法旺拉。
其他線程調(diào)用了interrupt中斷該線程。
時間間隔到了棵磷。
此時該線程就可以被調(diào)度了蛾狗,如果是被中斷的話就拋出一個InterruptedException異常。
notify
notifyAll
Java四種引用 --- 這里指的是“引用“仪媒,不是對象
強引用
平常我們使用對象的方式Object object = new Object();如果一個對象具有強引用沉桌,它就不會被垃圾回收器回收。即使當(dāng)前內(nèi)存空間不足算吩,JVM也不會回收它留凭,而是拋出OutOfMemoryError錯誤,使程序異常終止偎巢。例如下面的代碼:
publicclassMain{publicstaticvoidmain(String[] args){newMain().fun1();? ? }publicvoidfun1(){? ? ? ? Object object =newObject();? ? ? ? Object[] objArr =newObject[1000];? ? }}
當(dāng)運行至Object[] objArr = new Object[1000];這句時蔼夜,如果內(nèi)存不足,JVM會拋出OOM錯誤也不會回收object指向的對象压昼。不過要注意的是求冷,當(dāng)fun1運行完之后,object和objArr都已經(jīng)不存在了窍霞,所以它們指向的對象都會被JVM回收匠题。
但如果想中斷強引用和某個對象之間的關(guān)聯(lián),可以顯式地將引用賦值為null但金,這樣一來的話韭山,JVM在合適的時間就會回收該對象。
軟引用
軟引用通過SoftReference創(chuàng)建,在使用軟引用時钱磅,如果內(nèi)存的空間足夠巩踏,軟引用就能繼續(xù)被使用,而不會被垃圾回收器回收续搀,只有在內(nèi)存不足時塞琼,軟引用才會被垃圾回收器回收。
軟引用的這種特性使得它很適合用來解決OOM問題禁舷,實現(xiàn)緩存機制彪杉,例如:圖片緩存、網(wǎng)頁緩存等等……
軟引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用牵咙,如果軟引用所引用的對象被JVM回收派近,這個軟引用就會被加入到與之關(guān)聯(lián)的引用隊列中。
弱引用
事實上軟引用和弱引用非常類似洁桌,兩者的區(qū)別在于:只具有弱引用的對象擁有的生命周期更短暫渴丸。因為當(dāng) JVM 進(jìn)行垃圾回收,一旦發(fā)現(xiàn)弱引用對象另凌,無論當(dāng)前內(nèi)存空間是否充足谱轨,都會將弱引用回收。不過由于垃圾回收器是一個優(yōu)先級較低的線程吠谢,所以并不一定能迅速發(fā)現(xiàn)弱引用對象土童。
弱引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用,如果弱引用所引用的對象被JVM回收工坊,這個弱引用就會被加入到與之關(guān)聯(lián)的引用隊列中献汗。
虛引用
虛引用”顧名思義,就是形同虛設(shè)王污,與其他幾種引用都不同罢吃,虛引用并不會影響對象的生命周期。如果一個對象僅持有虛引用昭齐,那么它相當(dāng)于沒有引用尿招,在任何時候都可能被垃圾回收器回收。
強引用:不管什么時候都不會被回收司浪。
軟引用:當(dāng)內(nèi)存不足的時候泊业,JVM垃圾回收會回收把沼。
弱引用:不管內(nèi)存足不足啊易,只要發(fā)生JVM垃圾回收就會回收。
虛引用:隨時都可能會被回收饮睬。
小結(jié)
引用和引用隊列提供了一種通知機制租谈,允許我們知道對象已經(jīng)被銷毀或者即將被銷毀。GC要回收一個對象的時候,如果發(fā)現(xiàn)該對象有軟割去、弱窟却、虛引用的時候,會將這些引用加入到注冊的引用隊列中呻逆。軟引用和弱引用差別不大夸赫,JVM都是先把SoftReference和WeakReference中的referent字段值設(shè)置成null,之后加入到引用隊列咖城;而虛引用則不同茬腿,如果某個堆中的對象,只有虛引用宜雀,那么JVM會將PhantomReference加入到引用隊列中切平,JVM不會自動將referent字段值設(shè)置成null。
實際應(yīng)用:利用軟引用和弱引用緩存解決OOM問題辐董。
如:Bitmap的緩存
設(shè)計思路是:用一個HashMap來保存圖片的路徑和相應(yīng)圖片對象(Bitmap)的軟引用之間的映射關(guān)系悴品,在內(nèi)存不足時,JVM會自動回收這些緩存圖片對象所占用的空間简烘,從而有效地避免了OOM的問題苔严。在Android開發(fā)中對于大量圖片下載會經(jīng)常用到。
wait()孤澎、notify()和sleep()
wait()和notify()
wait()和notify()是直接隸屬于Object類邦蜜,在JAVA中的Object類型中,都是帶有一個內(nèi)存鎖的亥至,在有線程獲取該內(nèi)存鎖后悼沈,其它線程無法訪問該內(nèi)存,從而實現(xiàn)JAVA中簡單的同步姐扮、互斥操作絮供。明白這個原理,就能理解為什么synchronized(this)與synchronized(static XXX)的區(qū)別了茶敏,synchronized就是針對內(nèi)存區(qū)塊申請內(nèi)存鎖壤靶,this關(guān)鍵字代表類的一個對象,所以其內(nèi)存鎖是針對相同對象的互斥操作惊搏,而static成員屬于類專有贮乳,其內(nèi)存空間為該類所有成員共有,這就導(dǎo)致synchronized()對static成員加鎖恬惯,相當(dāng)于對類加鎖向拆,也就是在該類的所有成員間實現(xiàn)互斥,在同一時間只有一個線程可訪問該類的實例酪耳。wait只能由持有對像鎖的線程來調(diào)用浓恳。
Obj.wait()與Obj.notify()必須要與synchronized(Obj)一起使用,從功能上來說wait就是說線程在獲取對象鎖后,主動釋放對象鎖颈将,同時本線程休眠梢夯。直到有其它線程調(diào)用對象的notify()喚醒該線程,才能繼續(xù)獲取對象鎖晴圾,并繼續(xù)執(zhí)行颂砸。相應(yīng)的notify()就是對對象鎖的喚醒操作。但有一點需要注意的是notify()調(diào)用后死姚,并不是馬上就釋放對象鎖的沾凄,而是在相應(yīng)的synchronized(){}語句塊執(zhí)行結(jié)束,自動釋放鎖后知允,JVM會在wait()對象鎖的線程中隨機選取一線程撒蟀,賦予其對象鎖,喚醒線程温鸽,繼續(xù)執(zhí)行保屯。這樣就提供了在線程間同步、喚醒的操作涤垫。Thread.sleep()與Object.wait()二者都可以暫停當(dāng)前線程姑尺,釋放CPU控制權(quán),主要的區(qū)別在于Object.wait()在釋放CPU同時蝠猬,釋放了對象鎖的控制切蟋。
wait():促使當(dāng)前線程等待直到另外一個線程調(diào)用這個對象的notify()方法喚醒。和synchronized塊使用的時候榆芦,synchronized獲取對象的鎖以后柄粹,可以通過wait()方法釋放,同時阻塞當(dāng)前線程匆绣,停止執(zhí)行(這也是和sleep的區(qū)別)驻右。
publicstaticvoidfirstMethod(){synchronized(a){? ? ? ? ? ? System.out.println(Thread.currentThread().getName() +"? firstMethod--死鎖");try{//? ? ? ? ? ? ? ? Thread.sleep(10);a.wait();? ? ? ? ? ? }catch(InterruptedException e) {? ? ? ? ? ? ? ? e.printStackTrace();? ? ? ? ? ? }synchronized(b){? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName() +"? firstMethod--解鎖");? ? ? ? ? ? }? ? ? ? }? ? }publicstaticvoidseconedMethod(){synchronized(b){? ? ? ? ? ? System.out.println(Thread.currentThread().getName() +"? seconedMethod--死鎖");synchronized(a){? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName() +"? seconedMethod--解鎖");? ? ? ? ? ? ? ? a.notify();? ? ? ? ? ? }? ? ? ? }? ? }
如果用兩個線程分別執(zhí)行這兩個方法
publicstaticvoidmain(String[] args){? ? ? ? Runnable runnable1 =newRunnable() {@Overridepublicvoidrun(){? ? ? ? ? ? ? ? firstMethod();? ? ? ? ? ? }? ? ? ? };? ? ? ? Runnable runnable2 =newRunnable() {@Overridepublicvoidrun(){? ? ? ? ? ? ? ? seconedMethod();? ? ? ? ? ? }? ? ? ? };? ? ? ? Thread thread1 =newThread(runnable1);? ? ? ? Thread thread2 =newThread(runnable2);? ? ? ? thread1.start();? ? ? ? thread2.start();? ? }
如果是用sleep方法替換掉wait方法,就是一個死鎖崎淳,線程thread1先執(zhí)行拿到a對象的鎖堪夭,然后阻塞10ms(并沒有釋放鎖),thread2然后拿到對象b的鎖拣凹,這時候seconedMethod需要a對象的鎖森爽,但是firstMethod并沒有釋放,然后10ms過后嚣镜,firstMethod需要b的鎖爬迟,然后b的鎖也沒有在seconedMethod方法中釋放,兩個線程相互等待對方釋放鎖祈惶,就形成了死鎖雕旨。
運行結(jié)果:
Thread-0? firstMethod--死鎖
Thread-1? seconedMethod--死鎖
如果不使用sleep而是使用wait方法扮匠,就不會發(fā)生死鎖捧请。因為wait釋放了firstMethod中的a對象的鎖凡涩,當(dāng)seconedMethod需要a對象鎖的時候就可以用了。
運行結(jié)果:
Thread-0? firstMethod--死鎖
Thread-1? seconedMethod--死鎖
Thread-1? seconedMethod--解鎖
Thread-0? firstMethod--解鎖
notify():喚醒在此對象監(jiān)視器上等待的單個線程疹蛉。如果所有線程都在此對象上等待活箕,則會選擇喚醒其中一個線程(隨機)。直到當(dāng)前的線程放棄此對象上的鎖可款,才能繼續(xù)執(zhí)行被喚醒的線程育韩。
sleep()
通過Thread.sleep()使當(dāng)前線程暫停執(zhí)行一段時間枢纠,讓其他線程有機會繼續(xù)執(zhí)行李茫,但它并不釋放對象鎖。也就是說如果有synchronized同步塊派任,其他線程仍然不能訪問共享數(shù)據(jù)摸恍。注意該方法要捕捉異常悉罕。
publicclassThreadLock{? ? Object lock =newObject();intnum =0;publicstaticvoidmain(String[] args){? ? ? ? ThreadLock test =newThreadLock();? ? ? ? Runnable runnable =newRunnable() {@Overridepublicvoidrun(){? ? ? ? ? ? ? ? test.method2();? ? ? ? ? ? }? ? ? ? };? ? ? ? Thread thread1 =newThread(runnable);? ? ? ? thread1.start();? ? ? ? test.method1();? ? }publicvoidmethod1(){synchronized(lock){try{? ? ? ? ? ? ? ? Thread.sleep(1000);//? ? ? ? ? ? ? ? lock.wait(1000);num +=100;? ? ? ? ? ? }catch(InterruptedException e) {? ? ? ? ? ? ? ? e.printStackTrace();? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? }? ? }publicvoidmethod2(){synchronized(lock){? ? ? ? ? ? num +=9;? ? ? ? ? ? System.out.println(num);? ? ? ? }? ? }}
因為在main線程調(diào)用方法,因此先執(zhí)行主線程的method1立镶,對象鎖被主線程拿走了壁袄,那么子線程執(zhí)行method2的時候就需要等待1秒后把鎖還回來。
1秒后輸出結(jié)果:
109
如果替換成lock.wait(1000);
lock.wait(1000)會讓當(dāng)前線程(main線程)睡眠1秒媚媒,同時釋放synchronized的對象鎖嗜逻,因此小于1秒輸出9
synchronized和lock
幾個概念
共享變量(shared variable):多個線程都能訪問的變量。
變量可見性(variable visibility):一個線程更新了共享變量缭召,對其他線程立刻可見栈顷。
互斥(mutual exclusion ):幾個線程中的任何一個不能與其他一個或多個同時操作一個變量。
臨界區(qū)(critical section):訪問共享資源的一段代碼塊嵌巷。
synchronized
保證共享變量的可見性:變量緩存與編譯器指令優(yōu)化會導(dǎo)致變量修改的不可見性妨蛹。
保證共享變量的互斥性:同一時刻只能有一個線程對共享變量的修改(注意修改一次,是先讀再寫晴竞,是兩個操作)蛙卤。
特點:
當(dāng)程序運行到非靜態(tài)的synchronized同步方法上時,自動獲得與正在執(zhí)行代碼類的當(dāng)前實例(this實例)有關(guān)的鎖噩死。
如果兩個線程要執(zhí)行一個類中的synchronized方法颤难,并且兩個線程使用相同的實例來調(diào)用方法,那么一次只能有一個線程能夠執(zhí)行方法已维,另一個需要等待行嗤,直到鎖被釋放。也就是說:如果一個線程在對象上獲得一個鎖垛耳,就沒有任何其他線程可以進(jìn)入(該對象的)類中的任何一個同步方法栅屏。
線程可以獲得多個鎖飘千。比如,在一個對象的同步方法里面調(diào)用另外一個對象的同步方法栈雳,則獲取了兩個對象的同步鎖护奈。
線程同步方法是通過鎖來實現(xiàn),每個對象都有且僅有一個鎖哥纫,這個鎖與一個特定的對象關(guān)聯(lián)霉旗,線程一旦獲取了對象鎖,其他訪問該對象的線程就無法再訪問該對象的其他同步方法蛀骇。
對于同步厌秒,要時刻清醒在哪個對象上同步,這是關(guān)鍵擅憔。
死鎖是線程間相互等待鎖造成的鸵闪。
lock
lock提供了如下的方法:
void lock(),獲取一個鎖暑诸,如果鎖當(dāng)前被其他線程獲得蚌讼,當(dāng)前的線程將被休眠。
boolean tryLock()屠列,嘗試獲取一個鎖啦逆,如果當(dāng)前鎖被其他線程持有,則返回false笛洛,不會使當(dāng)前線程休眠夏志。
boolean tryLock(long timeout,TimeUnit unit),如果獲取了鎖定立即返回true苛让,如果別的線程正持有鎖沟蔑,會等待參數(shù)給定的時間,在等待的過程中狱杰,如果獲取了鎖定瘦材,就返回true,如果等待超時仿畸,返回false食棕。
void lockInterruptibly(),如果獲取了鎖定立即返回错沽,如果沒有獲取鎖定簿晓,當(dāng)前線程處于休眠狀態(tài),直到或者鎖定千埃,或者當(dāng)前線程被別的線程中斷憔儿。
synchronized和lock區(qū)別
synchronized是在JVM層面上實現(xiàn)的,如果代碼執(zhí)行時出現(xiàn)異常放可,JVM會自動釋放monitor鎖谒臼。而lock代碼是用戶寫的朝刊,需要用戶來保證最終釋放掉鎖。
lock提供了一個重要的方法newConditon()蜈缤,ConditionObject有await()拾氓、signal()、signalAll()劫樟,類似于Ojbect類的wait()痪枫、notify()织堂、notifyAll()叠艳。這些方法都是用來實現(xiàn)線程間通信。lock將synchronized的互斥性和線程間通信分離開來易阳,一個lock可以有多個condition附较。另外lock的signal可以實現(xiàn)公平的通知,而notify是隨機從鎖等待隊列中喚醒某個進(jìn)程潦俺。
性能上來說拒课,在多線程競爭比較激烈地情況下,lock比synchronize高效得多事示。
publicclassThreadLock{publicstaticvoidmain(String[] args){? ? ? ? Test test =newTest();? ? ? ? Lock lock =newReentrantLock();? ? ? ? Runnable runnable =newRunnable() {@Overridepublicvoidrun(){? ? ? ? ? ? ? ? ? ? lock.lock();for(inti =0; i <50; i++) {? ? ? ? ? ? ? ? ? ? ? ? test.setX(1);? ? ? ? ? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName() +" : "+test.getX());? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? lock.unlock();? ? ? ? ? ? ? ? }? ? ? ? };? ? ? ? Thread thread1 =newThread(runnable);? ? ? ? Thread thread2 =newThread(runnable);? ? ? ? thread1.start();? ? ? ? thread2.start();? ? }staticclassTest{privateintx =100;publicintgetX(){returnx;? ? ? ? }publicvoidsetX(inty){? ? ? ? ? ? x = x - y;? ? ? ? }? ? }}
ReentrantLock與synchronized的比較
ReentrantLocak(可重入鎖)
簡單來說早像,它有一個與鎖相關(guān)的獲取計數(shù)器,如果擁有鎖的某個線程再次得到鎖肖爵,那么獲取計數(shù)器就加1卢鹦,然后鎖需要被釋放兩次才能獲得真正釋放。
ReentrantLock提供了synchronized類似的功能和內(nèi)存語義劝堪。
不同
ReentrantLock功能性方面更全面冀自,比如時間鎖等候,可中斷鎖等候秒啦,鎖投票等熬粗,因此更有擴展性。在多個條件變量和高度競爭鎖的地方余境,用ReentrantLock更合適驻呐,ReentrantLock還提供了Condition,對線程的等待和喚醒等操作更加靈活芳来,一個ReentrantLock可以有多個Condition實例含末,所以更有擴展性。
ReentrantLock的性能比synchronized會好點绣张。
ReentrantLock提供了可輪詢的鎖請求答渔,他可以嘗試的去取得鎖,如果取得成功則繼續(xù)處理侥涵,取得不成功沼撕,可以等下次運行的時候處理宋雏,所以不容易產(chǎn)生死鎖,而synchronized則一旦進(jìn)入鎖請求要么成功务豺,要么一直阻塞磨总,所以更容易產(chǎn)生死鎖。
缺點
lock必須在finally 塊中釋放笼沥。否則蚪燕,如果受保護的代碼將拋出異常,鎖就有可能永遠(yuǎn)得不到釋放奔浅!這一點區(qū)別看起來可能沒什么馆纳,但是實際上,它極為重要汹桦。忘記在 finally 塊中釋放鎖鲁驶,可能會在程序中留下一個定時炸彈,當(dāng)有一天炸彈爆炸時舞骆,您要花費很大力氣才能找到源頭在哪钥弯。而使用同步,JVM 將確保鎖會獲得自動釋放督禽。
當(dāng)JVM用synchronized管理鎖定請求和釋放時脆霎,JVM在生成線程轉(zhuǎn)儲時能夠包括鎖定信息。這些對調(diào)試非常有價值狈惫,因為它們能標(biāo)識死鎖或者其他異常行為的來源睛蛛。Lock類只是普通的類,JVM不知道具體哪個線程擁有Lock對象虱岂。
ArrayList玖院,LinkedList和Vector
ArrayList和Vector都是基于數(shù)組實現(xiàn)的,所以查詢效率高第岖,插入效率低难菌。
LinkedList基于雙向鏈表實現(xiàn)的,所以插入效率高蔑滓,查詢效率低郊酒。
Vector使用了synchronized方法,所以線程安全键袱,性能比ArrayList低燎窘。
LinkedList實現(xiàn)了List接口,還提供了額外的get蹄咖,remove褐健,insert方法在LinkedList的首部或尾部,這些操作使LinkedList可被用作棧(Stack),隊列(Queue)或雙向隊列(deque)蚜迅。
ArrayList和LinkedList允許null元素舵匾,重復(fù)元素。
HashMap和HashTable
都實現(xiàn)了Map接口
HashMap允許key為null谁不,value為null而HashTable不允許坐梯,如果新加入的key和之前重復(fù)了,會覆蓋之前的value刹帕。
HashTable線程安全吵血,而HashMap不是線程安全。因此單線程下HashTable比HashMap慢偷溺。
HashMap不能保證隨著時間推移Map中的元素次序是不變的蹋辅。
Hashtable中hash數(shù)組默認(rèn)大小是11,增加的方式是old*2+1亡蓉。HashMap中hash數(shù)組的默認(rèn)大小是16晕翠,而且一定是2的指數(shù)喷舀。
ConcurrentHashMap
鎖分段技術(shù)
HashTable容器在競爭激烈的并發(fā)環(huán)境下表現(xiàn)出效率低下的原因是所有訪問HashTable的線程都必須競爭同一把鎖砍濒,那假如容器里有多把鎖,每一把鎖用于鎖容器其中一部分?jǐn)?shù)據(jù)硫麻,那么當(dāng)多線程訪問容器里不同數(shù)據(jù)段的數(shù)據(jù)時爸邢,線程間就不會存在鎖競爭,從而可以有效的提高并發(fā)訪問效率拿愧,這就是ConcurrentHashMap所使用的鎖分段技術(shù)杠河,首先將數(shù)據(jù)分成一段一段的存儲,然后給每一段數(shù)據(jù)配一把鎖浇辜,當(dāng)一個線程占用鎖訪問其中一個段數(shù)據(jù)的時候券敌,其他段的數(shù)據(jù)也能被其他線程訪問。
HashSet
實現(xiàn)了Set接口柳洋,HashSet< T >本質(zhì)就是一個HashMap待诅,把HashMap的key作為HashSet的值,HashMap的value是一個固定的Object對象熊镣,因為HashMap的key是不允許重復(fù)的卑雁,所以HashSet里的元素也是不能重復(fù)的,也可以看出HashSet的查詢效率很高绪囱。
String测蹲,StringBuilder和StringBuffer
CharSequence接口:一個字符序列。String鬼吵,StringBuilder和StringBuffer都實現(xiàn)了它扣甲。
String類:是常量,不可變.
StringBuilder類:只可以在單線程的情況下進(jìn)行修改(線程不安全)齿椅,字符串拼接用琉挖,除了StringBuffer可用場景外荷逞。
StringBuffer類:可以在多線程的情況下進(jìn)行改變(線程安全),比如:在http請求中拼接粹排。
StringBuilder比StringBuffer效率高,應(yīng)該盡量使用StringBuilder种远。
Excption與Error包結(jié)構(gòu)
結(jié)構(gòu)圖:
need-to-insert-img
結(jié)構(gòu)圖
Throwable
Throwable是Java語言中所有錯誤或異常的超類。
Throwable包含兩個子類:Error和Exception顽耳。它們通常用于指示發(fā)生了異常情況坠敷。
Throwable包含了其線程創(chuàng)建時線程執(zhí)行堆棧的快照,它提供了printStackTrace()等接口用于獲取堆棧跟蹤數(shù)據(jù)等信息射富。
Exception
Exception及其子類是Throwable的一種形式膝迎,它指出了合理的應(yīng)用程序想要捕獲的條件。
RuntimeException
RuntimeException是那些可能在Java虛擬機正常運行期間拋出的異常的超類胰耗。
編譯器不會檢查RuntimeException異常限次。 例如,除數(shù)為零時柴灯,拋出ArithmeticException異常卖漫。RuntimeException是ArithmeticException的超類。當(dāng)代碼發(fā)生除數(shù)為零的情況時赠群,倘若既"沒有通過throws聲明拋出ArithmeticException異常"羊始,也"沒有通過try...catch...處理該異常",也能通過編譯查描。這就是我們所說的"編譯器不會檢查RuntimeException異常"突委!
如果代碼會產(chǎn)生RuntimeException異常,則需要通過修改代碼進(jìn)行避免冬三。 例如匀油,若會發(fā)生除數(shù)為零的情況,則需要通過代碼避免該情況的發(fā)生勾笆!
Error
和Exception一樣敌蚜,Error也是Throwable的子類。 它用于指示合理的應(yīng)用程序不應(yīng)該試圖捕獲的嚴(yán)重問題匠襟,大多數(shù)這樣的錯誤都是異常條件钝侠。
和RuntimeException一樣, 編譯器也不會檢查Error酸舍。
Interface與abstract類的區(qū)別
參數(shù)抽象類接口
默認(rèn)的方法實現(xiàn)它可以有默認(rèn)的方法實現(xiàn)接口完全是抽象的帅韧。它根本不存在方法的實現(xiàn)
實現(xiàn)子類使用extends關(guān)鍵字來繼承抽象類。如果子類不是抽象類的話啃勉,它需要提供抽象類中所有聲明的方法的實現(xiàn)忽舟。子類使用關(guān)鍵字implements來實現(xiàn)接口。它需要提供接口中所有聲明的方法的實現(xiàn)
構(gòu)造器抽象類可以有構(gòu)造器接口不能有構(gòu)造器
與正常Java類的區(qū)別除了你不能實例化抽象類之外叮阅,它和普通Java類沒有任何區(qū)別接口是完全不同的類型
訪問修飾符抽象方法可以有public刁品、protected和default這些修飾符接口方法默認(rèn)修飾符是public。你不可以使用其它修飾符浩姥。
main方法抽象方法可以有main方法并且我們可以運行它接口沒有main方法挑随,因此我們不能運行它。
多繼承抽象類可以繼承一個類和實現(xiàn)多個接口接口只可以繼承一個或多個其它接口
速度它比接口速度要快接口是稍微有點慢的勒叠,因為它需要時間去尋找在類中實現(xiàn)的方法兜挨。
添加新方法如果你往抽象類中添加新的方法,你可以給它提供默認(rèn)的實現(xiàn)眯分。因此你不需要改變你現(xiàn)在的代碼拌汇。如果你往接口中添加方法,那么你必須改變實現(xiàn)該接口的類弊决。
靜態(tài)內(nèi)部類和非靜態(tài)內(nèi)部類
相同點
內(nèi)部類都可以用public噪舀,protected,private修飾飘诗。
方法中都可以調(diào)用外部類的靜態(tài)成員變量与倡。
不同點
靜態(tài)內(nèi)部類可以聲明靜態(tài)和非靜態(tài)成員變量,非靜態(tài)內(nèi)部類只能聲明非靜態(tài)成員變量疚察。
靜態(tài)內(nèi)部類可以聲明靜態(tài)和非靜態(tài)方法蒸走,非靜態(tài)內(nèi)部類只能聲明非靜態(tài)方法。
靜態(tài)內(nèi)部類不能調(diào)用外部類的非靜態(tài)成員變量(靜態(tài)方法和非靜態(tài)方法都一樣)貌嫡,非靜態(tài)內(nèi)部類都可以調(diào)用。
泛型擦除
泛型在JDK5以后才有的该溯,擦除是為了兼容之前沒有的使用泛型的類庫和代碼岛抄。如:ArrayList< String >和ArrayList< Integer >在編譯器編譯的時候都變成了ArrayList。
List list =newArrayList();Map map =newHashMap();System.out.println(Arrays.toString(list.getClass().getTypeParameters()));System.out.println(Arrays.toString(map.getClass().getTypeParameters()));/* Output
[E]
[K, V]
*/
我們期待的是得到泛型參數(shù)的類型狈茉,但是實際上我們只得到了一堆占位符夫椭。
publicclassMain{publicT[] makeArray() {// error: Type parameter 'T' cannot be instantiated directlyreturnnewT[5];? ? }}
我們無法在泛型內(nèi)部創(chuàng)建一個T類型的數(shù)組,原因也和之前一樣氯庆,T僅僅是個占位符蹭秋,并沒有真實的類型信息,實際上堤撵,除了new表達(dá)式之外仁讨,instanceof操作和轉(zhuǎn)型(會收到警告)在泛型內(nèi)部都是無法使用的,而造成這個的原因就是之前講過的編譯器對類型信息進(jìn)行了擦除实昨。
publicclassMain{privateT t;publicvoidset(T t){this.t = t;? ? }publicTget(){returnt;? ? }publicstaticvoidmain(String[] args){? ? ? ? Main m =newMain();? ? ? ? m.set("findingsea");? ? ? ? String s = m.get();? ? ? ? System.out.println(s);? ? }}/* Output
findingsea
*/
雖然有類型擦除的存在洞豁,使得編譯器在泛型內(nèi)部其實完全無法知道有關(guān)T的任何信息,但是編譯器可以保證重要的一點:內(nèi)部一致性,也是我們放進(jìn)去的是什么類型的對象丈挟,取出來還是相同類型的對象刁卜,這一點讓Java的泛型起碼還是有用武之地的。
代碼片段展現(xiàn)就是編譯器確保了我們放在T上的類型的確是T(即便它并不知道有關(guān)T的任何類型信息)曙咽。這種確保其實做了兩步工作:
set()處的類型檢驗
get()處的類型轉(zhuǎn)換
這兩步工作也成為邊界動作蛔趴。
publicclassMain{publicListfillList(T t,intsize){? ? ? ? List list =newArrayList();for(inti =0; i < size; i++) {? ? ? ? ? ? list.add(t);? ? ? ? }returnlist;? ? }publicstaticvoidmain(String[] args){? ? ? ? Main m =newMain();? ? ? ? List list = m.fillList("findingsea",5);? ? ? ? System.out.println(list.toString());? ? }}/* Output
[findingsea, findingsea, findingsea, findingsea, findingsea]
*/
代碼片段同樣展示的是泛型的內(nèi)部一致性。
擦除的補償
如上看到的例朱,但凡是涉及到確切類型信息的操作夺脾,在泛型內(nèi)部都是無法共工作的。那是否有辦法繞過這個問題來編程茉继,答案就是顯示地傳遞類型標(biāo)簽咧叭。
publicclassMain{publicTcreate(Class type){try{returntype.newInstance();? ? ? ? }catch(Exception e) {? ? ? ? ? ? e.printStackTrace();? ? ? ? }returnnull;? ? }publicstaticvoidmain(String[] args){? ? ? ? Main m =newMain();? ? ? ? String s = m.create(String.class);? ? }}代碼片段展示了一種用類型標(biāo)簽生成新對象的方法,但是這個辦法很脆弱烁竭,因為這種辦法要求對應(yīng)的類型必須有默認(rèn)構(gòu)造函數(shù)菲茬,遇到Integer類型的時候就失敗了,而且這個錯誤還不能在編譯器捕獲派撕。
publicclassMain{publicT[] create(Class type) {return(T[]) Array.newInstance(type,10);? ? }publicstaticvoidmain(String[] args){? ? ? ? Main m =newMain();? ? ? ? String[] strings = m.create(String.class);? ? }}代碼片段七展示了對泛型數(shù)組的擦除補償婉弹,本質(zhì)方法還是通過顯示地傳遞類型標(biāo)簽,通過Array.newInstance(type, size)來生成數(shù)組终吼,同時也是最為推薦的在泛型內(nèi)部生成數(shù)組的方法镀赌。