1. 對抽象鱼炒、繼承琴锭、多態(tài)的理解
抽象:表示對問題領(lǐng)域進(jìn)行分析、設(shè)計中得出的抽象的概念降瞳,是對一系列看上去不同嘱支,但是本質(zhì)上相同的具體概念的抽象,在java中抽象用 abstract 關(guān)鍵字來修飾挣饥,用 abstract 修飾類時除师,此類就不能被實例化,從這里可以看出扔枫,抽象類就是為了繼承而存在的汛聚,如果定義了一個抽象類而不去繼承它,那么等于白白創(chuàng)建了這個抽象類茧吊,因為你不能用它來做任何事情贞岭,用 abstract 修飾方法時,此方法就是抽象方法搓侄,抽象方法必須存在于抽象類中瞄桨,抽象方法沒有方法體,對于一個父類來說讶踪,如果它的某個方法在父類中實現(xiàn)出來沒有任何意義芯侥,必須根據(jù)子類的實際需求來進(jìn)行不同的實現(xiàn),那么就可以將這個方法聲明為抽象方法,抽象方法的修飾符必須為 public 或者 protected 柱查,應(yīng)為用 private廓俭,則不能被子類繼承,子類便無法實現(xiàn)該方法唉工,缺省情況下默認(rèn)為 public 研乒。
封裝:是面向?qū)ο蠓椒ǖ闹匾瓌t,就是把對象的屬性和行為(數(shù)據(jù))結(jié)合為一個獨(dú)立的整體淋硝,并盡可能隱藏對象的內(nèi)部實現(xiàn)細(xì)節(jié)雹熬,就是把不想告訴或者不該告訴別人的東西隱藏起來,把可以告訴別人的公開谣膳,別人只能用我提供的功能實現(xiàn)需求竿报,而不知道是如何實現(xiàn)的。增加安全性
繼承:是面向?qū)ο笞铒@著的一個特性继谚,繼承是從已有的類中派生出新的類稱為子類烈菌,子類繼承父類的數(shù)據(jù)屬性和行為,并能根據(jù)自己的需求擴(kuò)展出新的行為花履,提高了代碼的復(fù)用性芽世。
多態(tài):指允許不同的對象對同一消息做出相應(yīng)。即同一消息可以根據(jù)發(fā)送對象的不同而采用多種不同的行為方式(發(fā)送消息就是函數(shù)調(diào)用)臭挽。封裝和繼承幾乎都是為多態(tài)而準(zhǔn)備的捂襟,在執(zhí)行期間判斷引用對象的實際類型,根據(jù)其實際的類型調(diào)用其相應(yīng)的方法欢峰。
2. 泛型的作用及使用場景
Java從1.5之后支持泛型葬荷,泛型的本質(zhì)是參數(shù)化類型,也就是說所操作的數(shù)據(jù)類型被指定為一個參數(shù)纽帖。這種參數(shù)類型可以用在類宠漩、接口和方法的創(chuàng)建中,分別稱為泛型類懊直、泛型接口扒吁、泛型方法。
泛型是對 Java 語言的類型系統(tǒng)的一種擴(kuò)展室囊,以支持創(chuàng)建可以按類型進(jìn)行參數(shù)化的類雕崩。可以把類型參數(shù)看作是使用參數(shù)化類型時指定的類型的一個占位符融撞,就像方法的形式參數(shù)是運(yùn)行時傳遞的值的占位符一樣盼铁。
泛型可以消除代碼中的強(qiáng)制類型轉(zhuǎn)換,同時獲得一個附加的類型檢查層尝偎,該檢查層可以防止有人將錯誤類型的鍵或值保存在集合中饶火。這就是泛型所做的工作鹏控。
泛型的好處:
- 類型安全
- 消除強(qiáng)制類型轉(zhuǎn)換
- 潛在的性能收益。
Java語言引入泛型的好處是安全簡單肤寝。泛型的好處是在編譯的時候檢查類型安全当辐,并且所有的強(qiáng)制轉(zhuǎn)換都是自動和隱式的,提高代碼的重用率鲤看。
泛型在使用中還有一些規(guī)則和限制:
- 泛型的類型參數(shù)只能是類類型(包括自定義類)缘揪,不能是簡單類型。
- 同一種泛型可以對應(yīng)多個版本(因為參數(shù)類型是不確定的)刨摩,不同版本的泛型類實例是不兼容的寺晌。
- 泛型的類型參數(shù)可以有多個。
- 泛型的參數(shù)類型可以使用extends語句澡刹,例如<T extends superclass>。習(xí)慣上成為“有界類型”耘婚。
- 泛型的參數(shù)類型還可以是通配符類型罢浇。例如Class<?> classType = Class.forName(Java.lang.String);
使用泛型最常見的就是ArrayList<String>;目前集合框架以及封裝的各個模塊都要使用泛型沐祷。
泛型的使用場景:
1. 通配符
在開發(fā)中對象的引用傳遞是最常見的嚷闭,但是在泛型操作中,進(jìn)行引用傳遞的時候泛型必須匹配才可以傳遞赖临,否則無法傳遞胞锰。
class Info<T>{
private T var ; // 定義泛型變量
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){
// 直接打印
return this.var.toString() ;
}
};
public class GenericsDemo14{
public static void main(String args[]){
Info<String> i = new Info<String>() ; // 使用String為泛型類型
i.setVar("MLDN") ;
// 設(shè)置內(nèi)容
fun(i) ;
}
public static void fun(Info<?> temp){
// 可以接收任意的泛型對象
System.out.println("內(nèi)容:" + temp) ;
}
};
使用?可以接受任意類型的數(shù)據(jù)兢榨,卻無法進(jìn)行修改嗅榕,?為通配符吵聪。
2. 受限泛型
class Info<T>{
private T var ; // 定義泛型變量
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){
// 直接打印
return this.var.toString() ;
}
};
public class GenericsDemo17 {
public static void main(String[] args) {
Info<Integer> info1 = new Info<Integer>(); // 聲明Integer的泛型對象
Info<Float> info2 = new Info<Float>(); // 聲明Float的泛型對象
Info<String> info3 = new Info<String>();
info1.setVar(30); // 設(shè)置整數(shù)凌那,自動裝箱
info2.setVar(30.1F); // 設(shè)置小數(shù),自動裝箱
info3.setVar("俺是字符串吟逝,不能被受限的FUN組裝");
fun(info1);
fun(info2);
// fun(info3); //受限了帽蝶,不能調(diào)用這個
}
public static void fun(Info<? extends Number> temp){
// 只能接收String或Object類型的泛型
public static void fun(Info<? super String> temp){
System.out.println("內(nèi)容:"+temp);
}
}
}
public class GenericsDemo21{
public static void main(String args[]){
Info<String> i1 = new Info<String>() ; // 聲明String的泛型對象
Info<Object> i2 = new Info<Object>() ; // 聲明Object的泛型對象
i1.setVar("hello") ;
i2.setVar(new Object());
fun(i1) ;
fun(i2) ;
}
public static void fun(Info<? super String> temp){
// 只能接收String或Object類型的泛型
System.out.print(temp + "、") ;
}
};
如果設(shè)置成Stirng類型就會出現(xiàn)錯誤:
類型參數(shù) java.lang.String 不在其限制范圍之內(nèi)
Info<String> i1 = new Info<String>() ; // 聲明Integer的 泛型對象
String 不是Number的子類块攒,最高不能超過Number的子類励稳。
3. 泛型接口
訪問權(quán)限 +interface +接口名稱 + <泛型標(biāo)示>{}
泛型接口實現(xiàn)的兩種方式:
interface Info<T>{ // 在接口上定義泛型
public T getVar() ; // 定義抽象方法,抽象方法的返回值就是泛型類型
}
class InfoImpl implements Info<String>{ // 定義泛型接口的子類
private String var ; // 定義屬性
public InfoImpl(String var){
// 通過構(gòu)造方法設(shè)置屬性內(nèi)容
this.setVar(var) ;
}
public void setVar(String var){
this.var = var ;
}
public String getVar(){
return this.var ;
}
};
public class GenericsDemo{
public static void main(String arsg[]){
Info i = null; // 聲明接口對象
i = new InfoImpl("soyoungboy") ; // 通過子類實例化對象
System.out.println("內(nèi)容:" + i.getVar()) ;
}
};
interface Info<T>{ // 在接口上定義泛型
public T getVar() ; // 定義抽象方法囱井,抽象方法的返回值就是泛型類型
}
class InfoImpl<T> implements Info<T>{ // 定義泛型接口的子類
private T var ; // 定義屬性
public InfoImpl(T var){
// 通過構(gòu)造方法設(shè)置屬性內(nèi)容
this.setVar(var) ;
}
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
};
public class GenericsDemo{
public static void main(String arsg[]){
Info<String> i = null; // 聲明接口對象
i = new InfoImpl<String>("soyoungboy") ; // 通過子類實例化對象
System.out.println("內(nèi)容:" + i.getVar()) ;
}
};
4. 泛型方法
訪問權(quán)限 +<泛型標(biāo)示>+泛型標(biāo)示 方法名稱(泛型標(biāo)示 參數(shù)名稱)
class Demo{
public <T> T fun(T t){ // 可以接收任意類型的數(shù)據(jù)
return t ; // 直接把參數(shù)返回
}
};
public class GenericsDemo{
public static void main(String args[]){
Demo d = new Demo() ; // 實例化Demo對象
String str = d.fun("soyoungboy") ; // 傳遞字符串
int i = d.fun(30) ; // 傳遞數(shù)字驹尼,自動裝箱
System.out.println(str) ; // 輸出內(nèi)容
System.out.println(i) ; // 輸出內(nèi)容
}
};
3. 枚舉的特點及使用場景
基本特性:
- enum關(guān)鍵字
枚舉enum是同class,interface同一級別的特性琅绅。 - 枚舉的定義
如何在Enum中定義枚舉常量呢扶欣?
1,枚舉常量沒有任何修飾符
2,每個常量以“料祠,”分隔骆捧,以“;”結(jié)束枚舉常量的描述髓绽。
3敛苇,枚舉常量必須定義在所有方法或者構(gòu)造器之前。
//枚舉常量的定義可以在最后一個元素加顺呕,再加枫攀;結(jié)束。常量區(qū)分大小寫
public enum OrdinaryEnums {
red,
RED,
green,
yellow,
blue,;
}
- 使用枚舉的目的
枚舉的作用不僅僅可以讓你使用限制在一個enum中的變量株茶,并且這些變量的靈活性和拓展性很好来涨。
只要是需要控制變量的數(shù)量或者范圍,并且拿到變量后還需要處理一些邏輯的場景都可以用枚舉來完成启盛。
public enum WeekEnums {
//注:枚舉寫在最前面蹦掐,否則編譯出錯
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,;
private static String getWeek(WeekEnums weekEnums) {
String week = null;
switch (weekEnums) {
case Sunday://星期天
week = "星期天";
break;
case Monday://星期一
week = "星期一";
break;
case Tuesday:// 星期二
week = "星期二";
break;
case Wednesday://星期三
week = "星期三";
break;
case Thursday:// 星期四
week = "星期四";
break;
case Friday://星期五
week = "星期五";
break;
case Saturday:// 星期六
week = "星期六";
break;
}
return week;
}
}
//獲取方式:
String weekday = WeekEnums.getWeek(WeekEnums.Friday);
4.線程sleep和wait的區(qū)別
線程其實沒什么好說的,但凡說道線程問題僵闯,多半都是指多線程卧抗,可以參考多線程學(xué)習(xí)。
1. 基本特征:
線程和進(jìn)程一樣分為五個階段:創(chuàng)建鳖粟、就緒社裆、運(yùn)行、阻塞向图、終止泳秀。
- Thread.start()方法調(diào)用后并不是立即執(zhí)行多線程代碼,而是使得該線程變?yōu)榭蛇\(yùn)行態(tài)(Runnable)张漂,什么時候運(yùn)行是由操作系統(tǒng)決定的晶默。start()方法重復(fù)調(diào)用的話,會出現(xiàn)java.lang.IllegalThreadStateException異常航攒。
- 從程序運(yùn)行的結(jié)果可以發(fā)現(xiàn)磺陡,多線程程序是亂序執(zhí)行。
- Thread.sleep()方法調(diào)用目的是不讓當(dāng)前線程獨(dú)自霸占該進(jìn)程所獲取的CPU資源漠畜,以留出一定時間給其他線程執(zhí)行的機(jī)會币他。
實際上所有的多線程代碼執(zhí)行順序都是不確定的,每次執(zhí)行的結(jié)果都是隨機(jī)的憔狞。 - 在java中蝴悉,每次程序運(yùn)行至少啟動2個線程。一個是main線程瘾敢,一個是垃圾收集線程拍冠。
2.線程狀態(tài)轉(zhuǎn)換
1尿这、新建狀態(tài)(New):新創(chuàng)建了一個線程對象。
2庆杜、就緒狀態(tài)(Runnable):線程對象創(chuàng)建后射众,其他線程調(diào)用了該對象的start()方法。該狀態(tài)的線程位于可運(yùn)行線程池中晃财,變得可運(yùn)行叨橱,等待獲取CPU的使用權(quán)。
3断盛、運(yùn)行狀態(tài)(Running):就緒狀態(tài)的線程獲取了CPU罗洗,執(zhí)行程序代碼。
4钢猛、阻塞狀態(tài)(Blocked):阻塞狀態(tài)是線程因為某種原因放棄CPU使用權(quán)伙菜,暫時停止運(yùn)行。直到線程進(jìn)入就緒狀態(tài)厢洞,才有機(jī)會轉(zhuǎn)到運(yùn)行狀態(tài)仇让。阻塞的情況分三種:
(一)、等待阻塞:運(yùn)行的線程執(zhí)行wait()方法躺翻,JVM會把該線程放入等待池中。(wait會釋放持有的鎖)
(二)卫玖、同步阻塞:運(yùn)行的線程在獲取對象的同步鎖時公你,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池中假瞬。
(三)陕靠、其他阻塞:運(yùn)行的線程執(zhí)行sleep()或join()方法,或者發(fā)出了I/O請求時脱茉,JVM會把該線程置為阻塞狀態(tài)剪芥。當(dāng)sleep()狀態(tài)超時、join()等待線程終止或者超時琴许、或者I/O處理完畢時税肪,線程重新轉(zhuǎn)入就緒狀態(tài)。(注意,sleep是不會釋放持有的鎖)
5榜田、死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法益兄,該線程結(jié)束生命周期。
3. 線程調(diào)度
- 調(diào)整線程優(yōu)先級:Java線程有優(yōu)先級箭券,優(yōu)先級高的線程會獲得較多的運(yùn)行機(jī)會净捅。
- 線程睡眠:Thread.sleep(long millis)方法,使線程轉(zhuǎn)到阻塞狀態(tài)辩块。millis參數(shù)設(shè)定睡眠的時間蛔六,以毫秒為單位荆永。當(dāng)睡眠結(jié)束后,就轉(zhuǎn)為就緒(Runnable)狀態(tài)国章。
- 線程等待:Object類中的wait()方法具钥,導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對象的 notify() 方法或 notifyAll() 喚醒方法捉腥。
- 線程讓步:Thread.yield() 方法氓拼,暫停當(dāng)前正在執(zhí)行的線程對象,把執(zhí)行機(jī)會讓給相同或者更高優(yōu)先級的線程抵碟。
- 線程加入:join()方法桃漾,等待其他線程終止。在當(dāng)前線程中調(diào)用另一個線程的join()方法拟逮,則當(dāng)前線程轉(zhuǎn)入阻塞狀態(tài)撬统,直到另一個進(jìn)程運(yùn)行結(jié)束,當(dāng)前線程再由阻塞轉(zhuǎn)為就緒狀態(tài)敦迄。
- 線程喚醒:Object類中的notify()方法恋追,喚醒在此對象監(jiān)視器上等待的單個線程。如果所有線程都在此對象上等待罚屋,則會選擇喚醒其中一個線程苦囱。選擇是任意性的,并在對實現(xiàn)做出決定時發(fā)生脾猛。線程通過調(diào)用其中一個 wait 方法撕彤,在對象的監(jiān)視器上等待。 直到當(dāng)前的線程放棄此對象上的鎖定猛拴,才能繼續(xù)執(zhí)行被喚醒的線程羹铅。被喚醒的線程將以常規(guī)方式與在該對象上主動同步的其他所有線程進(jìn)行競爭;例如愉昆,喚醒的線程在作為鎖定此對象的下一個線程方面沒有可靠的特權(quán)或劣勢职员。類似的方法還有一個notifyAll(),喚醒在此對象監(jiān)視器上等待的所有線程跛溉。
- Thread.sleep()與Object.wait()二者都可以暫停當(dāng)前線程焊切,釋放CPU控制權(quán),主要的區(qū)別在于Object.wait()在釋放CPU同時倒谷,釋放了對象鎖的控制蛛蒙。
附贈一個牛逼到例子:
建立三個線程,A線程打印10次A渤愁,B線程打印10次B,C線程打印10次C牵祟,要求線程同時運(yùn)行,交替打印10次ABC抖格。這個問題用Object的wait()诺苹,notify()就可以很方便的解決咕晋。代碼如下:
/**
* wait用法
* @author DreamSea
* @time 2015.3.9
*/
package com.multithread.wait;
public class MyThreadPrinter2 implements Runnable {
private String name;
private Object prev;
private Object self;
private MyThreadPrinter2(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}
@Override
public void run() {
int count = 10;
while (count > 0) {
synchronized (prev) {
synchronized (self) {
System.out.print(name);
count--;
self.notify();
}
try {
prev.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);
MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);
MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);
new Thread(pa).start();
Thread.sleep(100); //確保按順序A、B收奔、C執(zhí)行
new Thread(pb).start();
Thread.sleep(100);
new Thread(pc).start();
Thread.sleep(100);
}
}
輸出結(jié)果:
ABCABCABCABCABCABCABCABCABCABC
先來解釋一下其整體思路掌呜,從大的方向上來講,該問題為三線程間的同步喚醒操作坪哄,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循環(huán)執(zhí)行三個線程质蕉。為了控制線程執(zhí)行的順序,那么就必須要確定喚醒翩肌、等待的順序模暗,所以每一個線程必須同時持有兩個對象鎖,才能繼續(xù)執(zhí)行念祭。一個對象鎖是prev兑宇,就是前一個線程所持有的對象鎖。還有一個就是自身對象鎖粱坤。主要的思想就是隶糕,為了控制執(zhí)行的順序,必須要先持有prev鎖站玄,也就前一個線程要釋放自身對象鎖枚驻,再去申請自身對象鎖,兩者兼?zhèn)鋾r打印株旷,之后首先調(diào)用self.notify()釋放自身對象鎖测秸,喚醒下一個等待線程,再調(diào)用prev.wait()釋放prev對象鎖灾常,終止當(dāng)前線程,等待循環(huán)結(jié)束后再次被喚醒铃拇。運(yùn)行上述代碼钞瀑,可以發(fā)現(xiàn)三個線程循環(huán)打印ABC,共10次慷荔。程序運(yùn)行的主要過程就是A線程最先運(yùn)行雕什,持有C,A對象鎖,后釋放A,C鎖显晶,喚醒B贷岸。線程B等待A鎖,再申請B鎖磷雇,后打印B偿警,再釋放B,A鎖唯笙,喚醒C螟蒸,線程C等待B鎖盒使,再申請C鎖,后打印C七嫌,再釋放C,B鎖少办,喚醒A∷性看起來似乎沒什么問題英妓,但如果你仔細(xì)想一下,就會發(fā)現(xiàn)有問題绍赛,就是初始條件蔓纠,三個線程按照A,B,C的順序來啟動,按照前面的思考惹资,A喚醒B贺纲,B喚醒C,C再喚醒A褪测。但是這種假設(shè)依賴于JVM中線程調(diào)度猴誊、執(zhí)行的順序。
5. wait和sleep區(qū)別
共同點:
- 他們都是在多線程的環(huán)境下侮措,都可以在程序的調(diào)用處阻塞指定的毫秒數(shù)懈叹,并返回。
- wait()和sleep()都可以通過interrupt()方法 打斷線程的暫停狀態(tài) 分扎,從而使線程立刻拋出InterruptedException澄成。
如果線程A希望立即結(jié)束線程B,則可以對線程B對應(yīng)的Thread實例調(diào)用interrupt方法畏吓。如果此刻線程B正在wait/sleep /join墨状,則線程B會立刻拋出InterruptedException,在catch() {} 中直接return即可安全地結(jié)束線程菲饼。
需要注意的是肾砂,InterruptedException是線程自己從內(nèi)部拋出的,并不是interrupt()方法拋出的宏悦。對某一線程調(diào)用 interrupt()時镐确,如果該線程正在執(zhí)行普通的代碼,那么該線程根本就不會拋出InterruptedException饼煞。但是源葫,一旦該線程進(jìn)入到 wait()/sleep()/join()后,就會立刻拋出InterruptedException 砖瞧。
不同點:
- Thread類的方法:sleep(),yield()等
Object的方法:wait()和notify()等 - 每個對象都有一個鎖來控制同步訪問息堂。Synchronized關(guān)鍵字可以和對象的鎖交互,來實現(xiàn)線程的同步芭届。
sleep方法沒有釋放鎖储矩,而wait方法釋放了鎖感耙,使得其他線程可以使用同步控制塊或者方法。 - wait持隧,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用即硼,而sleep可以在任何地方使用
所以sleep()和wait()方法的最大區(qū)別是:
sleep()睡眠時,保持對象鎖屡拨,仍然占有該鎖只酥;而wait()睡眠時,釋放對象鎖呀狼。
但是wait()和sleep()都可以通過interrupt()方法打斷線程的暫停狀態(tài)裂允,從而使線程立刻拋出InterruptedException(但不建議使用該方法)。
重要:Java中sleep()與wait()的區(qū)別總結(jié)
對于sleep()方法哥艇,我們首先要知道該方法是屬于Thread類中的绝编。而wait()方法,則是屬于Object類中的貌踏。
sleep()方法導(dǎo)致了程序暫停執(zhí)行指定的時間十饥,讓出cpu該其他線程,但是他的監(jiān)控狀態(tài)依然保持者祖乳,當(dāng)指定的時間到了又會自動恢復(fù)運(yùn)行狀態(tài)逗堵。
在調(diào)用sleep()方法的過程中,線程不會釋放對象鎖眷昆。
而當(dāng)調(diào)用wait()方法的時候蜒秤,線程會放棄對象鎖,進(jìn)入等待此對象的等待鎖定池亚斋,只有針對此對象調(diào)用notify()方法后本線程才進(jìn)入對象鎖定池準(zhǔn)備
獲取對象鎖進(jìn)入運(yùn)行狀態(tài)作媚。
舉個例子:
/**
* java中的sleep()和wait()的區(qū)別
* @author Hongten
* @date 2013-12-10
*/
public class TestD {
public static void main(String[] args) {
new Thread(new Thread1()).start();
try {
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(new Thread2()).start();
}
private static class Thread1 implements Runnable{
@Override
public void run(){
synchronized (TestD.class) {
System.out.println("enter thread1...");
System.out.println("thread1 is waiting...");
try {
//調(diào)用wait()方法,線程會放棄對象鎖帅刊,進(jìn)入等待此對象的等待鎖定池
TestD.class.wait();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("thread1 is going on ....");
System.out.println("thread1 is over!!!");
}
}
}
private static class Thread2 implements Runnable{
@Override
public void run(){
synchronized (TestD.class) {
System.out.println("enter thread2....");
System.out.println("thread2 is sleep....");
//只有針對此對象調(diào)用notify()方法后本線程才進(jìn)入對象鎖定池準(zhǔn)備獲取對象鎖進(jìn)入運(yùn)行狀態(tài)掂骏。
TestD.class.notify();
//==================
//區(qū)別
//如果我們把代碼:TestD.class.notify();給注釋掉,即TestD.class調(diào)用了wait()方法厚掷,但是沒有調(diào)用notify()
//方法,則線程永遠(yuǎn)處于掛起狀態(tài)级解。
try {
//sleep()方法導(dǎo)致了程序暫停執(zhí)行指定的時間冒黑,讓出cpu該其他線程,
//但是他的監(jiān)控狀態(tài)依然保持者勤哗,當(dāng)指定的時間到了又會自動恢復(fù)運(yùn)行狀態(tài)抡爹。
//在調(diào)用sleep()方法的過程中,線程不會釋放對象鎖芒划。
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("thread2 is going on....");
System.out.println("thread2 is over!!!");
}
}
}
}
運(yùn)行效果:
enter thread1...
thread1 is waiting...
enter thread2....
thread2 is sleep....
thread2 is going on....
thread2 is over!!!
thread1 is going on ....
thread1 is over!!!
如果注釋掉代碼:
TestD.class.notify();
運(yùn)行效果:
enter thread1...
thread1 is waiting...
enter thread2....
thread2 is sleep....
thread2 is going on....
thread2 is over!!!
5. JAVA反射機(jī)制
詳情參考
什么是Java反射機(jī)制冬竟?
JAVA反射機(jī)制是在運(yùn)行狀態(tài)中欧穴,對于任意一個類,都能夠知道這個類的所有屬性和方法泵殴;對于任意一個對象涮帘,都能夠調(diào)用它的任意一個方法;這種動態(tài)獲取的以及動態(tài)調(diào)用對象的方法的功能稱為Java的反射機(jī)制笑诅。
另:Java的反射機(jī)制在平時的業(yè)務(wù)開發(fā)過程中很少使用到调缨,但是在一些基礎(chǔ)框架的搭建上應(yīng)用非常廣泛。
6. weak/soft/strong引用的區(qū)別
強(qiáng)引用
就是new出來的吆你,不會被垃圾回收弦叶。
軟引用
當(dāng)對象是Soft reference可達(dá)時,gc會向系統(tǒng)申請更多內(nèi)存妇多,而不是直接回收它伤哺,當(dāng)內(nèi)存不足的時候才回收它。因此Soft reference適合用于構(gòu)建一些緩存系統(tǒng)者祖,比如圖片緩存立莉。
弱引用
WeakReference被GC回收的可能性較大,在使用它之前咸包,你需要通過weakObj.get()去判斷目的對象引用是否已經(jīng)被回收桃序。
7. Object的hashCode()與equals()的區(qū)別和作用
這個要總結(jié)起來就太麻煩了,還是參考大神的吧烂瘫。參考資料
HashSet和HashMap一直都是JDK中最常用的兩個類媒熊,HashSet要求不能存儲相同的對象,HashMap要求不能存儲相同的鍵坟比。 那么Java運(yùn)行時環(huán)境是如何判斷HashSet中相同對象芦鳍、HashMap中相同鍵的呢?當(dāng)存儲了“相同的東西”之后Java運(yùn)行時環(huán)境又將如何來維護(hù)呢葛账?
在研究這個問題之前柠衅,首先說明一下JDK對equals(Object obj)和hashcode()這兩個方法的定義和規(guī)范:
在Java中任何一個對象都具備equals(Object obj)和hashcode()這兩個方法,因為他們是在Object類中定義的籍琳。
equals(Object obj)方法用來判斷兩個對象是否“相同”菲宴,如果“相同”則返回true,否則返回false趋急。
hashcode()方法返回一個int數(shù)喝峦,在Object類中的默認(rèn)實現(xiàn)是“將該對象的內(nèi)部地址轉(zhuǎn)換成一個整數(shù)返回”箕母。
接下來有兩個個關(guān)于這兩個方法的重要規(guī)范(我只是抽取了最重要的兩個,其實不止兩個):
規(guī)范1:若重寫equals(Object obj)方法诡蜓,有必要重寫hashcode()方法,確保通過equals(Object obj)方法判斷結(jié)果為true的兩個對象具備相等的hashcode()返回值垢乙。說得簡單點就是:“如果兩個對象相同,那么他們的hashcode應(yīng)該 相等”眉踱。不過請注意:這個只是規(guī)范挤忙,如果你非要寫一個類讓equals(Object obj)返回true而hashcode()返回兩個不相等的值,編譯和運(yùn)行都是不會報錯的谈喳。不過這樣違反了Java規(guī)范册烈,程序也就埋下了BUG。
規(guī)范2:如果equals(Object obj)返回false叁执,即兩個對象“不相同”茄厘,并不要求對這兩個對象調(diào)用hashcode()方法得到兩個不相同的數(shù)。說的簡單點就是:“如果兩個對象不相同谈宛,他們的hashcode可能相同”次哈。
根據(jù)這兩個規(guī)范,可以得到如下推論: ☆ ☆ ☆ ☆
1吆录、如果兩個對象equals窑滞,Java運(yùn)行時環(huán)境會認(rèn)為他們的hashcode一定相等。
2恢筝、如果兩個對象不equals哀卫,他們的hashcode有可能相等。
3撬槽、如果兩個對象hashcode相等此改,他們不一定equals。
4侄柔、如果兩個對象hashcode不相等共啃,他們一定不equals。
這樣我們就可以推斷Java運(yùn)行時環(huán)境是怎樣判斷HashSet和HastMap中的兩個對象相同或不同了暂题。我的推斷是:先判斷hashcode是否相等移剪,再判斷是否equals。
測試程序如下:首先我們定義一個類薪者,重寫hashCode()和equals(Object obj)方法
Java代碼
class A {
@Override
public boolean equals(Object obj) {
System.out.println("判斷equals");
return false;
}
@Override
public int hashCode() {
System.out.println("判斷hashcode");
return 1;
}
}
然后寫一個測試類纵苛,代碼如下:
Java代碼
public class Test {
public static void main(String[] args) {
Map<A,Object> map = new HashMap<A, Object>();
map.put(new A(), new Object());
map.put(new A(), new Object());
System.out.println(map.size());
}
}
運(yùn)行之后打印結(jié)果是:
判斷hashcode
判斷hashcode
判斷equals
2
可以看出,Java運(yùn)行時環(huán)境會調(diào)用new A()這個對象的hashcode()方法言津。其中:
打印出的第一行“判斷hashcode”是第一次map.put(new A(), new Object())所打印出的攻人。
接下來的“判斷hashcode”和“判斷equals”是第二次map.put(new A(), new Object())所打印出來的。
那么為什么會是這樣一個打印結(jié)果呢悬槽?我是這樣分析的:
1贝椿、當(dāng)?shù)谝淮蝝ap.put(new A(), new Object())的時候,Java運(yùn)行時環(huán)境就會判斷這個map里面有沒有和現(xiàn)在添加的 new A()對象相同的鍵陷谱,判斷方法:調(diào)用new A()對象的hashcode()方法,判斷map中當(dāng)前是不是存在和new A()對象相同的HashCode。顯然烟逊,這時候沒有相同的渣窜,因為這個map中都還沒有東西。所以這時候hashcode不相等宪躯,則沒有必要再調(diào)用 equals(Object obj)方法了乔宿。參見推論4(如果兩個對象hashcode不相等,他們一定不equals)
2访雪、當(dāng)?shù)诙蝝ap.put(new A(), new Object())的時候详瑞,Java運(yùn)行時環(huán)境再次判斷,這時候發(fā)現(xiàn)了map中有兩個相同的hashcode(因為我重寫了A類的hashcode()方 法永遠(yuǎn)都返回1)臣缀,所以有必要調(diào)用equals(Object obj)方法進(jìn)行判斷了坝橡。參見推論3(如果兩個對象hashcode相等,他們不一定equals)精置,然后發(fā)現(xiàn)兩個對象不equals(因為我重寫了equals(Object obj)方法计寇,永遠(yuǎn)都返回false)。
3脂倦、這時候判斷結(jié)束番宁,判斷結(jié)果:兩次存入的對象不是相同的對象。所以最后打印map的長度的時候顯示結(jié)果是:2赖阻。
改寫程序如下:
Java代碼
class A {
@Override
public boolean equals(Object obj) {
System.out.println("判斷equals");
return true;
}
@Override
public int hashCode() {
System.out.println("判斷hashcode");
return 1;
}
}
public class Test {
public static void main(String[] args) {
Map<A,Object> map = new HashMap<A, Object>();
map.put(new A(), new Object());
map.put(new A(), new Object());
System.out.println(map.size());
}
}
運(yùn)行之后打印結(jié)果是:
判斷hashcode
判斷hashcode
判斷equals
1
顯然這時候map的長度已經(jīng)變成1了蝶押,因為Java運(yùn)行時環(huán)境認(rèn)為存入了兩個相同的對象。原因可根據(jù)上述分析方式進(jìn)行分析火欧。
以上分析的是HashMap棋电,其實HashSet的底層本身就是通過HashMap來實現(xiàn)的,所以他的判斷原理和HashMap是一樣的布隔,也是先判斷hashcode再判斷equals离陶。
所以:寫程序的時候應(yīng)盡可能的按規(guī)范來,不然在不知不覺中就埋下了bug衅檀!