最近發(fā)現(xiàn)自己的Java基礎(chǔ)知識還是有點薄弱婴洼,剛好有點空閑時間進(jìn)行再補(bǔ)一補(bǔ)蒂阱,然后進(jìn)行整理一下洪乍,方便自己以后復(fù)習(xí)。其實個人認(rèn)為Java基礎(chǔ)還是很重要的开瞭,不管從事Java后端開發(fā)還是Android開發(fā)懒震,Java這塊的基礎(chǔ)還是重中之重罩息,可以多去學(xué)習(xí)一下Java各種類和數(shù)據(jù)結(jié)構(gòu)的寫法,進(jìn)行學(xué)習(xí)挎狸!
基礎(chǔ)
正確使用 equals 方法
盡量使用 "字符串".equals(變量)方法扣汪,推薦使用java.util.Objects#equals(JDK7 引入的工具類)
Objects.equals(null,"SnailClimb");// false
java.util.Objects#equals源碼:
public static boolean equals(Object a, Object b) {
// 可以避免空指針異常。如果a==null的話此時a.equals(b)就不會得到執(zhí)行锨匆,避免出現(xiàn)空指針異常崭别。
return (a == b) || (a != null && a.equals(b));
}
BigDecimal
浮點數(shù)之間的等值判斷,基本數(shù)據(jù)類型不能用==來比較恐锣,包裝數(shù)據(jù)類型不能用 equals 來判斷,會造成精度丟失問題,不要使用構(gòu)造方法BigDecimal(double)方式把double值轉(zhuǎn)化為BigDecimal對象茅主,推薦使用BigDecimal(String)方法來定義浮點數(shù)的值,再進(jìn)行浮點數(shù)的運(yùn)算操作土榴。
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);// 0.1
BigDecimal y = b.subtract(c);// 0.1
System.out.println(x.equals(y));// true
BigDecimal 的大小比較:
a.compareTo(b) : 返回 -1 表示小于诀姚,0 表示 等于, 1表示 大于玷禽。
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
System.out.println(a.compareTo(b));// 1
基本數(shù)據(jù)類型與包裝數(shù)據(jù)類型的使用標(biāo)準(zhǔn)
- 【強(qiáng)制】所有的 POJO 類屬性必須使用包裝數(shù)據(jù)類型赫段。
- 【強(qiáng)制】RPC 方法的返回值和參數(shù)必須使用包裝數(shù)據(jù)類型。
- 【推薦】所有的局部變量使用基本數(shù)據(jù)類型矢赁。
Arrays.asList()使用指南
Arrays.asList()將數(shù)組轉(zhuǎn)換為集合后,底層其實還是數(shù)組糯笙,并沒有實現(xiàn)修改集合的方法,所以不能使用其修改集合的相關(guān)方法撩银,add/remove/clear方法會拋出UnsupportedOperationException異常
傳遞的數(shù)組必須是對象數(shù)組给涕,而不是基本類型(需要使用包裝數(shù)據(jù)類型)。
如何將數(shù)組轉(zhuǎn)換成ArrayList:
1.最簡便的方法(推薦)
List list = new ArrayList<>(Arrays.asList("a", "b", "c"))
2.使用 Java8 的Stream
Integer [] myArray = { 1, 2, 3 };
List myList = Arrays.stream(myArray).collect(Collectors.toList());
//基本類型也可以實現(xiàn)轉(zhuǎn)換(依賴boxed的裝箱操作)
int [] myArray2 = { 1, 2, 3 };
List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());
Collection.toArray()方法使用的坑
該方法是一個泛型方法:<T> T[] toArray(T[] a); 如果toArray方法中沒有傳遞任何參數(shù)的話返回的是Object類型數(shù)組额获。
String [] s= new String[]{
"dog", "lazy", "a", "over", "jumps", "fox", "brown", "quick", "A"
};
List<String> list = Arrays.asList(s);
Collections.reverse(list);
s=list.toArray(new String[0]);//沒有指定類型的話會報錯
// new String[0]起一個模板的作用够庙,指定了返回數(shù)組的類型,0是為了節(jié)省空間抄邀,因為它只是為了說明返回的類型
不要在 foreach 循環(huán)里進(jìn)行元素的 remove/add 操作
remove元素使用Iterator方式耘眨,如果是并發(fā)操作,需要對Itreator對象加鎖境肾。foreach循環(huán)會拋出ConcurrentModificationException異常
String StringBuffer 和 StringBuilder 的區(qū)別是什么? String 為什么是不可變的?
-
可變性
String 類中使用 final 關(guān)鍵字修飾字符數(shù)組來保存字符串 private final char value[]所以 String 對象是不可變的.而StringBuilder 與 StringBuffer 都繼承自 AbstractStringBuilder 類毅桃,在 AbstractStringBuilder 中也是使用字符數(shù)組保存字符串char[]value 但是沒有用 final 關(guān)鍵字修飾 -
線程安全性
String 中的對象是不可變的,也就可以理解為常量准夷,線程安全。StringBuffer 對方法加了同步鎖或者對調(diào)用的方法加了同步鎖莺掠,所以是線程安全的衫嵌。StringBuilder 并沒有對方法進(jìn)行加同步鎖,所以是非線程安全的彻秆。 -
性能
每次對 String 類型進(jìn)行改變的時候楔绞,都會生成一個新的 String 對象结闸,然后將指針指向新的 String 對象。StringBuffer 每次都會對 StringBuffer 對象本身進(jìn)行操作酒朵,而不是生成新的對象并改變對象引用桦锄。
對于三者使用的總結(jié):
操作少量的數(shù)據(jù): 適用String
單線程操作字符串緩沖區(qū)下操作大量數(shù)據(jù): 適用StringBuilder
多線程操作字符串緩沖區(qū)下操作大量數(shù)據(jù): 適用StringBuffer
在 Java 中定義一個不做事且沒有參數(shù)的構(gòu)造方法的作用
Java 程序在執(zhí)行子類的構(gòu)造方法之前,如果沒有用 super() 來調(diào)用父類特定的構(gòu)造方法蔫耽,則會調(diào)用父類中“沒有參數(shù)的構(gòu)造方法”结耀。因此,如果父類中只定義了有參數(shù)的構(gòu)造方法匙铡,而在子類的構(gòu)造方法中又沒有用 super() 來調(diào)用父類中特定的構(gòu)造方法图甜,則編譯時將發(fā)生錯誤,因為 Java 程序在父類中找不到?jīng)]有參數(shù)的構(gòu)造方法可供執(zhí)行鳖眼。解決辦法是在父類里加上一個不做事且沒有參數(shù)的構(gòu)造方法黑毅。
接口和抽象類的區(qū)別
- 接口的方法默認(rèn)是 public,所有方法在接口中不能有實現(xiàn)(Java 8 開始接口方法可以有默認(rèn)實現(xiàn))钦讳,而抽象類可以有非抽象的方法矿瘦。
- 接口中除了static、final變量愿卒,不能有其他變量缚去,而抽象類中則不一定。
- 一個類可以實現(xiàn)多個接口掘猿,但只能繼承一個抽象類病游。接口自己本身可以通過extends關(guān)鍵字?jǐn)U展多個接口。
- 接口方法默認(rèn)修飾符是public稠通,抽象方法可以有public衬衬、protected和default這些修飾符(抽象方法就是為了被重寫所以不能使用private關(guān)鍵字修飾!)改橘。
- 從設(shè)計層面來說滋尉,抽象是對類的抽象,是一種模板設(shè)計飞主,而接口是對行為的抽象狮惜,是一種行為的規(guī)范。
構(gòu)造方法的特性
- 名字與類名相同碌识。
- 沒有返回值碾篡,但不能用void聲明構(gòu)造函數(shù)。
- 生成類的對象時自動執(zhí)行筏餐,無需調(diào)用开泽。
== 與 equals(重要)
== : 它的作用是判斷兩個對象的地址是不是相等。即魁瞪,判斷兩個對象是不是同一個對象(基本數(shù)據(jù)類型==比較的是值穆律,引用數(shù)據(jù)類型==比較的是內(nèi)存地址)惠呼。
equals() : 它的作用也是判斷兩個對象是否相等。但它一般有兩種使用情況:
- 情況1:類沒有覆蓋 equals() 方法峦耘。則通過 equals() 比較該類的兩個對象時剔蹋,等價于通過“==”比較這兩個對象。
- 情況2:類覆蓋了 equals() 方法辅髓。一般泣崩,我們都覆蓋 equals() 方法來比較兩個對象的內(nèi)容是否相等;若它們的內(nèi)容相等利朵,則返回 true (即律想,認(rèn)為這兩個對象相等)。
hashCode 與 equals (重要)
hashCode() 的作用就是獲取哈希碼绍弟,也稱為散列碼技即;它實際上是返回一個int整數(shù)。這個哈希碼的作用是確定該對象在哈希表中的索引位置樟遣。hashCode() 在散列表中才有用而叼,在其它情況下沒用。在散列表中hashCode() 的作用是獲取對象的散列碼豹悬,進(jìn)而確定該對象在散列表中的位置葵陵。
hashCode()與equals()的相關(guān)規(guī)定
- 如果兩個對象相等,則hashcode一定也是相同的
- 兩個對象相等,對兩個對象分別調(diào)用equals方法都返回true
- 兩個對象有相同的hashcode值瞻佛,它們也不一定是相等的
- 因此脱篙,equals 方法被覆蓋過,則 hashCode 方法也必須被覆蓋
- hashCode() 的默認(rèn)行為是對堆上的對象產(chǎn)生獨特值伤柄。如果沒有重寫 hashCode()绊困,則該 class 的兩個對象無論如何都不會相等(即使這兩個對象指向相同的數(shù)據(jù))
Java異常處理
- try 塊:用于捕獲異常。其后可接零個或多個catch塊适刀,如果沒有catch塊秤朗,則必須跟一個finally塊。
- catch 塊:用于處理try捕獲到的異常笔喉。
- finally 塊:無論是否捕獲或處理異常取视,finally塊里的語句都會被執(zhí)行。當(dāng)在try塊或catch塊中遇到return語句時常挚,finally語句塊將在方法返回之前被執(zhí)行作谭。
在以下4種特殊情況下,finally塊不會被執(zhí)行:
- 在finally語句塊第一行發(fā)生了異常奄毡。 因為在其他行折欠,finally塊還是會得到執(zhí)行
- 在前面的代碼中用了System.exit(int)已退出程序。 exit是帶參函數(shù) ;若該語句在異常語句之后怨酝,finally會執(zhí)行
- 程序所在的線程死亡。
- 關(guān)閉CPU那先。
當(dāng)try語句和finally語句中都有return語句時农猬,在方法返回之前,finally語句的內(nèi)容將被執(zhí)行售淡,并且finally語句的返回值將會覆蓋原始的返回值斤葱。
Java序列化中如果有些字段不想進(jìn)行序列化,如何做
對于不想進(jìn)行序列化的變量揖闸,使用transient關(guān)鍵字修飾揍堕。
transient關(guān)鍵字的作用是:阻止實例中那些用此關(guān)鍵字修飾的的變量序列化;當(dāng)對象被反序列化時汤纸,被transient修飾的變量值不會被持久化和恢復(fù)衩茸。transient只能修飾變量,不能修飾類和方法贮泞。
Java 中只有值傳遞
Java程序設(shè)計語言總是采用按值調(diào)用楞慈。方法得到的是所有參數(shù)值的一個拷貝,即方法不能修改傳遞給它的任何參數(shù)變量的內(nèi)容啃擦。
- 一個方法不能修改一個基本數(shù)據(jù)類型的參數(shù)(即數(shù)值型或布爾型)囊蓝。
- 一個方法可以改變一個對象參數(shù)的狀態(tài)。
- 一個方法不能讓對象參數(shù)引用一個新的對象令蛉。
容器
List,Set,Map三者的區(qū)別
- List(對付順序的好幫手): List接口存儲一組不唯一(可以有多個元素引用相同的對象)聚霜,有序的對象
- Set(注重獨一無二的性質(zhì)): 不允許重復(fù)的集合。不會有多個元素引用相同的對象珠叔。
- Map(用Key來搜索的專家): 使用鍵值對存儲蝎宇。Map會維護(hù)與Key有關(guān)聯(lián)的值。兩個Key可以引用相同的對象运杭,但Key不能重復(fù)夫啊,典型的Key是String類型,但也可以是任何對象辆憔。
Arraylist 與 LinkedList 區(qū)別
- 是否保證線程安全:ArrayList 和LinkedList 都是不同步的撇眯,也就是不保證線程安全;
- 底層數(shù)據(jù)結(jié)構(gòu): Arraylist 底層使用的是** Object 數(shù)組虱咧;LinkedList 底層使用的是雙向鏈表**數(shù)據(jù)結(jié)構(gòu)(JDK1.6之前為循環(huán)鏈表熊榛,JDK1.7取消了循環(huán)。注意雙向鏈表和雙向循環(huán)鏈表的區(qū)別)
-
插入和刪除是否受元素位置的影響:① ArrayList 采用數(shù)組存儲腕巡,所以插入和刪除元素的時間復(fù)雜度受元素位置的影響玄坦。 比如:執(zhí)行add(E e) 方法的時候, ArrayList 會默認(rèn)在將指定的元素追加到此列表的末尾,這種情況時間復(fù)雜度就是O(1)煎楣。但是如果要在指定位置 i 插入和刪除元素的話(add(int index, E element) )時間復(fù)雜度就為 O(n-i)豺总。因為在進(jìn)行上述操作的時候集合中第 i 和第 i 個元素之后的(n-i)個元素都要執(zhí)行向后位/向前移一位的操作幔崖。 ② LinkedList 采用鏈表存儲项鬼,所以插入,刪除元素時間復(fù)雜度不受元素位置的影響佣赖,都是近似 O(1)而數(shù)組為近似 O(n)困曙。
4.是否支持快速隨機(jī)訪問:LinkedList 不支持高效的隨機(jī)元素訪問表伦,而 ArrayList 支持】独觯快速隨機(jī)訪問就是通過元素的序號快速獲取元素對象(對應(yīng)于get(int index) 方法)蹦哼。 - 內(nèi)存空間占用:ArrayList的空間浪費主要體現(xiàn)在在list列表的結(jié)尾會預(yù)留一定的容量空間,而LinkedList的空間花費則體現(xiàn)在它的每一個元素都需要消耗比ArrayList更多的空間(因為要存放直接后繼和直接前驅(qū)以及數(shù)據(jù))要糊。
RandomAccess接口
RandomAccess 接口中什么都沒有定義,標(biāo)識實現(xiàn)這個接口的類具有隨機(jī)訪問功能(知識標(biāo)識纲熏,沒有具體作用)。
在 binarySearch()方法中杨耙,它要判斷傳入的list 是否 RamdomAccess 的實例赤套,如果是,調(diào)用indexedBinarySearch()方法珊膜,如果不是容握,那么調(diào)用iteratorBinarySearch()方法
- 實現(xiàn)了 RandomAccess 接口的list,優(yōu)先選擇普通 for 循環(huán) 车柠,其次 foreach,
- 未實現(xiàn) RandomAccess接口的list剔氏,優(yōu)先選擇iterator遍歷(foreach遍歷底層也是通過iterator實現(xiàn)的,),大size的數(shù)據(jù)竹祷,千萬不要使用普通for循環(huán)
ArrayList 與 Vector 的區(qū)別谈跛,為什么要用Arraylist取代Vector
Vector類的所有方法都是同步的∷芰辏可以由兩個線程安全地訪問一個Vector對象感憾、但是一個線程訪問Vector的話代碼要在同步操作上耗費大量的時間。
Arraylist不是同步的令花,所以在不需要保證線程安全時建議使用Arraylist
通過ArrayList 源碼探索其擴(kuò)容機(jī)制
ArrayList有三種方式來初始化阻桅,構(gòu)造方法源碼如下:
/**
* 默認(rèn)初始容量大小
*/
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
*默認(rèn)構(gòu)造函數(shù),使用初始容量10構(gòu)造一個空列表(無參數(shù)構(gòu)造)
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 帶初始容量參數(shù)的構(gòu)造函數(shù)兼都。(用戶自己指定容量)
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {//初始容量大于0
//創(chuàng)建initialCapacity大小的數(shù)組
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//初始容量等于0
//創(chuàng)建空數(shù)組
this.elementData = EMPTY_ELEMENTDATA;
} else {//初始容量小于0嫂沉,拋出異常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
*構(gòu)造包含指定collection元素的列表,這些元素利用該集合的迭代器按順序返回
*如果指定的集合為null扮碧,throws NullPointerException趟章。
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
以無參數(shù)構(gòu)造方法創(chuàng)建 ArrayList 時杏糙,實際上初始化賦值的是一個空數(shù)組。當(dāng)真正對數(shù)組進(jìn)行添加元素操作時蚓土,才真正分配容量宏侍。即向數(shù)組中添加第一個元素時,數(shù)組容量擴(kuò)為10蜀漆。
- add 方法
/**
* 將指定的元素追加到此列表的末尾负芋。
*/
public boolean add(E e) {
//添加元素之前,先調(diào)用ensureCapacityInternal方法
ensureCapacityInternal(size + 1); // Increments modCount!!
//這里看到ArrayList添加元素的實質(zhì)就相當(dāng)于為數(shù)組賦值
elementData[size++] = e;
return true;
}
- ensureCapacityInternal() 方法
//得到最小擴(kuò)容量
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 獲取默認(rèn)的容量和傳入?yún)?shù)的較大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
當(dāng) 要 add 進(jìn)第1個元素時嗜愈,minCapacity為1,在Math.max()方法比較后莽龟,minCapacity 為10蠕嫁。
- ensureExplicitCapacity() 方法
//判斷是否需要擴(kuò)容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//調(diào)用grow方法進(jìn)行擴(kuò)容,調(diào)用此方法代表已經(jīng)開始擴(kuò)容了
grow(minCapacity);
}
- grow() 方法
/**
* 要分配的最大數(shù)組大小
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* ArrayList擴(kuò)容的核心方法毯盈。
*/
private void grow(int minCapacity) {
// oldCapacity為舊容量剃毒,newCapacity為新容量
int oldCapacity = elementData.length;
//將oldCapacity 右移一位,其效果相當(dāng)于oldCapacity /2搂赋,
//我們知道位運(yùn)算的速度遠(yuǎn)遠(yuǎn)快于整除運(yùn)算赘阀,整句運(yùn)算式的結(jié)果就是將新容量更新為舊容量的1.5倍,
int newCapacity = oldCapacity + (oldCapacity >> 1);
//然后檢查新容量是否大于最小需要容量脑奠,若還是小于最小需要容量基公,那么就把最小需要容量當(dāng)作數(shù)組的新容量,
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新容量大于 MAX_ARRAY_SIZE,進(jìn)入(執(zhí)行) `hugeCapacity()` 方法來比較 minCapacity 和 MAX_ARRAY_SIZE宋欺,
//如果minCapacity大于最大容量轰豆,則新容量則為`Integer.MAX_VALUE`,否則齿诞,新容量大小則為 MAX_ARRAY_SIZE 即為 `Integer.MAX_VALUE - 8`酸休。
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
- hugeCapacity() 方法。
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//對minCapacity和MAX_ARRAY_SIZE進(jìn)行比較
//若minCapacity大祷杈,將Integer.MAX_VALUE作為新數(shù)組的大小
//若MAX_ARRAY_SIZE大斑司,將MAX_ARRAY_SIZE作為新數(shù)組的大小
//MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
- java 中的 length 屬性針對數(shù)組,比如說你聲明了一個數(shù)組,想知道這個數(shù)組的長度則用到了 length 這個屬性.
- java 中的 length() 方法針對字符串,如果想看這個字符串的長度則用到 length() 這個方法.
- java 中的 size()方法針對泛型集合,如果想看這個泛型有多少個元素,就調(diào)用此方法來查看.
ArrayList源碼中的ensureCapacity方法
最好在 add 大量元素之前用 ensureCapacity 方法,以減少增量重新分配的次數(shù)
/**
如有必要但汞,增加此 ArrayList 實例的容量宿刮,以確保它至少可以容納由minimum capacity參數(shù)指定的元素數(shù)。
*
* @param minCapacity 所需的最小容量
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
HashMap 和 Hashtable 的區(qū)別
- 線程是否安全: HashMap 是非線程安全的特占,HashTable 是線程安全的糙置;HashTable 內(nèi)部的方法基本都經(jīng)過synchronized 修飾(要保證線程安全的話就使用 ConcurrentHashMap);
- 效率:因為線程安全的問題是目,HashMap 要比 HashTable 效率高一點谤饭。另外,HashTable 基本被淘汰,不要在代碼中使用它揉抵;
- 對Null key 和Null value的支持:HashMap 中亡容,null 可以作為鍵,這樣的鍵只有一個冤今,可以有一個或多個鍵所對應(yīng)的值為 null闺兢。但是在 HashTable 中 put 進(jìn)的鍵值只要有一個 null,直接拋出NullPointerException戏罢。
- 初始容量大小和每次擴(kuò)充容量大小的不同 : ①創(chuàng)建時如果不指定容量初始值屋谭,Hashtable 默認(rèn)的初始大小為11,之后每次擴(kuò)充龟糕,容量變?yōu)樵瓉淼?n+1桐磁。HashMap 默認(rèn)的初始化大小為16。之后每次擴(kuò)充讲岁,容量變?yōu)樵瓉淼?倍我擂。②創(chuàng)建時如果給定了容量初始值,那么 Hashtable 會直接使用你給定的大小缓艳,而 HashMap 會將其擴(kuò)充為2的冪次方大行DΑ(HashMap 中的tableSizeFor()方法保證)。也就是說 HashMap 總是使用2的冪作為哈希表的大小阶淘。
- 底層數(shù)據(jù)結(jié)構(gòu): JDK1.8 以后的 HashMap 在解決哈希沖突時有了較大的變化衙吩,當(dāng)鏈表長度大于閾值(默認(rèn)為8)時,將鏈表轉(zhuǎn)化為紅黑樹溪窒,以減少搜索時間分井。Hashtable 沒有這樣的機(jī)制。
HasMap 中帶有初始容量的構(gòu)造函數(shù):
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
tableSizeFor方法保證了 HashMap 總是使用2的冪作為哈希表的大小霉猛。
/**
* Returns a power of two size for the given target capacity.
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
集合框架底層數(shù)據(jù)結(jié)構(gòu)總結(jié)
- List
- Arraylist: Object數(shù)組
- Vector: Object數(shù)組
- LinkedList: 雙向鏈表(JDK1.6之前為循環(huán)鏈表尺锚,JDK1.7取消了循環(huán))
- Set
- HashSet(無序,唯一): 基于 HashMap 實現(xiàn)的惜浅,底層采用 HashMap 來保存元素
- LinkedHashSet: LinkedHashSet 繼承與 HashSet瘫辩,并且其內(nèi)部是通過 LinkedHashMap 來實現(xiàn)的。
- TreeSet(有序坛悉,唯一): 紅黑樹(自平衡的排序二叉樹伐厌。)
- Map
- HashMap: JDK1.8之前HashMap由數(shù)組+鏈表組成的,數(shù)組是HashMap的主體裸影,鏈表則是主要為了解決哈希沖突而存在的(“拉鏈法”解決沖突)挣轨。JDK1.8以后在解決哈希沖突時有了較大的變化,當(dāng)鏈表長度大于閾值(默認(rèn)為8)時轩猩,將鏈表轉(zhuǎn)化為紅黑樹卷扮,以減少搜索時間
- LinkedHashMap: LinkedHashMap 繼承自 HashMap荡澎,所以它的底層仍然是基于拉鏈?zhǔn)缴⒘薪Y(jié)構(gòu)即由數(shù)組和鏈表或紅黑樹組成。另外晤锹,LinkedHashMap 在上面結(jié)構(gòu)的基礎(chǔ)上摩幔,增加了一條雙向鏈表,使得上面的結(jié)構(gòu)可以保持鍵值對的插入順序鞭铆。同時通過對鏈表進(jìn)行相應(yīng)的操作或衡,實現(xiàn)了訪問順序相關(guān)邏輯。詳細(xì)可以查看:《LinkedHashMap 源碼詳細(xì)分析(JDK1.8)》
- Hashtable: 數(shù)組+鏈表組成的车遂,數(shù)組是 HashMap 的主體封断,鏈表則是主要為了解決哈希沖突而存在的
- TreeMap: 紅黑樹(自平衡的排序二叉樹)
并發(fā)
synchronized 關(guān)鍵字
synchronized關(guān)鍵字解決的是多個線程之間訪問資源的同步性,synchronized關(guān)鍵字可以保證被它修飾的方法或者代碼塊在任意時刻只能有一個線程執(zhí)行舶担。
synchronized關(guān)鍵字最主要的三種使用方式:
- 修飾實例方法: 作用于當(dāng)前對象實例加鎖澄港,進(jìn)入同步代碼前要獲得當(dāng)前對象實例的鎖
- 修飾靜態(tài)方法: :也就是給當(dāng)前類加鎖,會作用于類的所有對象實例柄沮。訪問靜態(tài) synchronized 方法占用的鎖是當(dāng)前類的鎖,而訪問非靜態(tài) synchronized 方法占用的鎖是當(dāng)前實例對象鎖废岂。
- 修飾代碼塊: 指定加鎖對象祖搓,對給定對象加鎖,進(jìn)入同步代碼庫前要獲得給定對象的鎖湖苞。
總結(jié): synchronized 關(guān)鍵字加到 static 靜態(tài)方法和 synchronized(class)代碼塊上都是是給 Class 類上鎖拯欧。synchronized 關(guān)鍵字加到實例方法上是給對象實例上鎖。盡量不要使用 synchronized(String a) 因為JVM中财骨,字符串常量池具有緩存功能镐作!
雙重校驗鎖實現(xiàn)對象單例(線程安全)
public class Singleton {
private volatile static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
//先判斷對象是否已經(jīng)實例過,沒有實例化過才進(jìn)入加鎖代碼
if (instance== null) {
//類對象加鎖
synchronized (Singleton.class) {
if (instance== null) {
instance= new Singleton();
}
}
}
return instance;
}
}
instance采用 volatile 關(guān)鍵字修飾也是很有必要的隆箩, instance= new Singleton(); 這段代碼其實是分為三步執(zhí)行:
- 為 instance分配內(nèi)存空間
- 初始化 instance
- 將 instance指向分配的內(nèi)存地址
但是由于 JVM 具有指令重排的特性该贾,執(zhí)行順序有可能變成 1->3->2。指令重排在單線程環(huán)境下不會出先問題捌臊,但是在多線程環(huán)境下會導(dǎo)致一個線程獲得還沒有初始化的實例杨蛋。例如,線程 T1 執(zhí)行了 1 和 3理澎,此時 T2 調(diào)用 getInstance() 后發(fā)現(xiàn) instance不為空逞力,因此返回 instance,但此時 instance還未被初始化糠爬。
使用 volatile 可以禁止 JVM 的指令重排寇荧,保證在多線程環(huán)境下也能正常運(yùn)行。
synchronized 關(guān)鍵字和 volatile 關(guān)鍵字的區(qū)別
- volatile關(guān)鍵字是線程同步的輕量級實現(xiàn)执隧,所以volatile性能肯定比synchronized關(guān)鍵字要好揩抡。但是volatile關(guān)鍵字只能用于變量而synchronized關(guān)鍵字可以修飾方法以及代碼塊户侥。synchronized關(guān)鍵字在JavaSE1.6之后進(jìn)行了主要包括為了減少獲得鎖和釋放鎖帶來的性能消耗而引入的偏向鎖和輕量級鎖以及其它各種優(yōu)化之后執(zhí)行效率有了顯著提升,實際開發(fā)中使用 synchronized 關(guān)鍵字的場景還是更多一些捅膘。
- 多線程訪問volatile關(guān)鍵字不會發(fā)生阻塞添祸,而synchronized關(guān)鍵字可能會發(fā)生阻塞
- volatile關(guān)鍵字能保證數(shù)據(jù)的可見性,但不能保證數(shù)據(jù)的原子性寻仗。synchronized關(guān)鍵字兩者都能保證刃泌。
- volatile關(guān)鍵字主要用于解決變量在多個線程之間的可見性,而 synchronized關(guān)鍵字解決的是多個線程之間訪問資源的同步性署尤。
ThreadLocal
通常情況下耙替,我們創(chuàng)建的變量是可以被任何一個線程訪問并修改的。如果想實現(xiàn)每一個線程都有自己的專屬本地變量該如何解決呢曹体? JDK中提供的ThreadLocal
類正是為了解決這樣的問題俗扇。 ThreadLocal
類主要解決的就是讓每個線程綁定自己的值,可以將ThreadLocal
類形象的比喻成存放數(shù)據(jù)的盒子箕别,盒子中可以存儲每個線程的私有數(shù)據(jù)铜幽。
如果你創(chuàng)建了一個ThreadLocal
變量,那么訪問這個變量的每個線程都會有這個變量的本地副本串稀,這也是ThreadLocal
變量名的由來除抛。他們可以使用 get()
和 set()
方法來獲取默認(rèn)值或?qū)⑵渲蹈臑楫?dāng)前線程所存的副本的值,從而避免了線程安全問題母截。
ThreadLocal原理
Thread
類中有一個 threadLocals
和 一個 inheritableThreadLocals
變量到忽,它們都是 ThreadLocalMap
類型的變量,我們可以把 ThreadLocalMap
理解為ThreadLocal
類實現(xiàn)的定制化的 HashMap
。默認(rèn)情況下這兩個變量都是null清寇,只有當(dāng)前線程調(diào)用 ThreadLocal
類的 set
或get
方法時才創(chuàng)建它們喘漏,實際上調(diào)用這兩個方法的時候,我們調(diào)用的是ThreadLocalMap
類對應(yīng)的 get()
华烟、set()
方法翩迈。
ThreadLocal
類的set()
方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
最終的變量是放在了當(dāng)前線程的 ThreadLocalMap
中,并不是存在 ThreadLocal
上盔夜,ThreadLocal
可以理解為只是ThreadLocalMap
的封裝帽馋,傳遞了變量值。 ThrealLocal
類中可以通過Thread.currentThread()
獲取到當(dāng)前線程對象后比吭,直接通過getMap(Thread t)
可以訪問到該線程的ThreadLocalMap
對象绽族。
每個Thread
中都具備一個ThreadLocalMap
,而ThreadLocalMap
可以存儲以ThreadLocal
為key的鍵值對衩藤。 比如我們在同一個線程中聲明了兩個 ThreadLocal
對象的話吧慢,會使用 Thread
內(nèi)部都是使用僅有那個ThreadLocalMap
存放數(shù)據(jù)的,ThreadLocalMap
的 key 就是 ThreadLocal
對象赏表,value 就是 ThreadLocal
對象調(diào)用set
方法設(shè)置的值检诗。ThreadLocal
是 map結(jié)構(gòu)是為了讓每個線程可以關(guān)聯(lián)多個 ThreadLocal
變量匈仗。這也就解釋了 ThreadLocal 聲明的變量為什么在每一個線程都有自己的專屬本地變量。
ThreadLocalMap
是ThreadLocal
的靜態(tài)內(nèi)部類逢慌。
ThreadLocal 內(nèi)存泄露問題
ThreadLocalMap
中使用的 key 為 ThreadLocal
的弱引用,而 value 是強(qiáng)引用悠轩。所以,如果 ThreadLocal
沒有被外部強(qiáng)引用的情況下攻泼,在垃圾回收的時候會 key 會被清理掉火架,而 value 不會被清理掉。這樣一來忙菠,ThreadLocalMap
中就會出現(xiàn)key為null的Entry何鸡。假如我們不做任何措施的話,value 永遠(yuǎn)無法被GC 回收牛欢,這個時候就可能會產(chǎn)生內(nèi)存泄露骡男。ThreadLocalMap實現(xiàn)中已經(jīng)考慮了這種情況,在調(diào)用 set()
傍睹、get()
隔盛、remove()
方法的時候,會清理掉 key 為 null 的記錄拾稳。使用完 ThreadLocal
方法后 最好手動調(diào)用remove()
方法
線程池
線程池提供了一種限制和管理資源(包括執(zhí)行一個任務(wù))吮炕。 每個線程池還維護(hù)一些基本統(tǒng)計信息,例如已完成任務(wù)的數(shù)量熊赖。
使用線程池的好處:
- 降低資源消耗。 通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗虑椎。
- 提高響應(yīng)速度震鹉。 當(dāng)任務(wù)到達(dá)時,任務(wù)可以不需要的等到線程創(chuàng)建就能立即執(zhí)行捆姜。
- 提高線程的可管理性传趾。 線程是稀缺資源,如果無限制的創(chuàng)建泥技,不僅會消耗系統(tǒng)資源浆兰,還會降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一的分配珊豹,調(diào)優(yōu)和監(jiān)控簸呈。
實現(xiàn)Runnable接口和Callable接口的區(qū)別
如果想讓線程池執(zhí)行任務(wù)的話需要實現(xiàn)的Runnable接口或Callable接口。 Runnable接口或Callable接口實現(xiàn)類都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor執(zhí)行店茶。兩者的區(qū)別在于 Runnable 接口不會返回結(jié)果但是 Callable 接口可以返回結(jié)果蜕便。
備注: 工具類Executors
可以實現(xiàn)Runnable
對象和Callable
對象之間的相互轉(zhuǎn)換。(Executors.callable(Runnable task)
或Executors.callable(Runnable task贩幻,Object resule)
)轿腺。
執(zhí)行execute()方法和submit()方法的區(qū)別
1)execute()
方法用于提交不需要返回值的任務(wù)两嘴,所以無法判斷任務(wù)是否被線程池執(zhí)行成功與否;
2)submit()
方法用于提交需要返回值的任務(wù)族壳。線程池會返回一個Future類型的對象憔辫,通過這個Future對象可以判斷任務(wù)是否執(zhí)行成功,并且可以通過future的get()方法來獲取返回值仿荆,get()方法會阻塞當(dāng)前線程直到任務(wù)完成,而使用 get(long timeout赖歌,TimeUnit unit)
方法則會阻塞當(dāng)前線程一段時間后立即返回枉圃,這時候有可能任務(wù)沒有執(zhí)行完。
如何創(chuàng)建線程池
《阿里巴巴Java開發(fā)手冊》中強(qiáng)制線程池不允許使用 Executors 去創(chuàng)建庐冯,而是通過 ThreadPoolExecutor 的方式孽亲,這樣的處理方式能更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險
Executors 返回線程池對象的弊端如下:
- FixedThreadPool 和 SingleThreadExecutor : 允許請求的隊列長度為 Integer.MAX_VALUE 展父,可能堆積大量的請求返劲,從而導(dǎo)致OOM。
- CachedThreadPool 和 ScheduledThreadPool : 允許創(chuàng)建的線程數(shù)量為 Integer.MAX_VALUE 栖茉,可能會創(chuàng)建大量線程篮绿,從而導(dǎo)致OOM。
方式一:通過構(gòu)造方法實現(xiàn)
方式二:通過Executor 框架的工具類Executors來實現(xiàn) 我們可以創(chuàng)建三種類型的ThreadPoolExecutor:
- FixedThreadPool : 該方法返回一個固定線程數(shù)量的線程池吕漂。該線程池中的線程數(shù)量始終不變亲配。當(dāng)有一個新的任務(wù)提交時,線程池中若有空閑線程惶凝,則立即執(zhí)行吼虎。若沒有,則新的任務(wù)會被暫存在一個任務(wù)隊列中苍鲜,待有線程空閑時思灰,便處理在任務(wù)隊列中的任務(wù)。
- SingleThreadExecutor: 方法返回一個只有一個線程的線程池混滔。若多余一個任務(wù)被提交到該線程池洒疚,任務(wù)會被保存在一個任務(wù)隊列中,待線程空閑坯屿,按先入先出的順序執(zhí)行隊列中的任務(wù)油湖。
- CachedThreadPool: 該方法返回一個可根據(jù)實際情況調(diào)整線程數(shù)量的線程池。線程池的線程數(shù)量不確定领跛,但若有空閑線程可以復(fù)用肺魁,則會優(yōu)先使用可復(fù)用的線程。若所有線程均在工作隔节,又有新的任務(wù)提交鹅经,則會創(chuàng)建新的線程處理任務(wù)寂呛。所有線程在當(dāng)前任務(wù)執(zhí)行完畢后,將返回線程池進(jìn)行復(fù)用瘾晃。
進(jìn)程和線程
進(jìn)程
進(jìn)程是程序的一次執(zhí)行過程贷痪,是系統(tǒng)運(yùn)行程序的基本單位,因此進(jìn)程是動態(tài)的蹦误。系統(tǒng)運(yùn)行一個程序即是一個進(jìn)程從創(chuàng)建劫拢,運(yùn)行到消亡的過程。
在 Java 中强胰,當(dāng)我們啟動 main 函數(shù)時其實就是啟動了一個 JVM 的進(jìn)程舱沧,而 main 函數(shù)所在的線程就是這個進(jìn)程中的一個線程,也稱主線程偶洋。
線程
線程與進(jìn)程相似熟吏,但線程是一個比進(jìn)程更小的執(zhí)行單位。一個進(jìn)程在其執(zhí)行的過程中可以產(chǎn)生多個線程玄窝。與進(jìn)程不同的是同類的多個線程共享進(jìn)程的堆和方法區(qū)資源牵寺,但每個線程有自己的程序計數(shù)器、虛擬機(jī)棧和本地方法棧恩脂,所以系統(tǒng)在產(chǎn)生一個線程帽氓,或是在各個線程之間作切換工作時,負(fù)擔(dān)要比進(jìn)程小得多俩块,也正因為如此黎休,線程也被稱為輕量級進(jìn)程。
線程與進(jìn)程的關(guān)系,區(qū)別及優(yōu)缺點
從 JVM 角度說進(jìn)程和線程之間的關(guān)系
一個進(jìn)程中可以有多個線程玉凯,多個線程共享進(jìn)程的堆和方法區(qū) (JDK1.8 之后的元空間)資源势腮,但是每個線程有自己的程序計數(shù)器、虛擬機(jī)棧 和 本地方法棧壮啊。
總結(jié): 線程是進(jìn)程劃分成的更小的運(yùn)行單位嫉鲸。線程和進(jìn)程最大的不同在于基本上各進(jìn)程是獨立的撑蒜,而各線程則不一定歹啼,因為同一進(jìn)程中的線程極有可能會相互影響。線程執(zhí)行開銷小座菠,但不利于資源的管理和保護(hù)狸眼;而進(jìn)程正相反
程序計數(shù)器、虛擬機(jī)棧和本地方法棧是線程私有的浴滴,堆和方法區(qū)是線程共享的
程序計數(shù)器為什么是私有的?
程序計數(shù)器主要有下面兩個作用:
- 字節(jié)碼解釋器通過改變程序計數(shù)器來依次讀取指令拓萌,從而實現(xiàn)代碼的流程控制,如:順序執(zhí)行升略、選擇微王、循環(huán)屡限、異常處理。
- 在多線程的情況下炕倘,程序計數(shù)器用于記錄當(dāng)前線程執(zhí)行的位置钧大,從而當(dāng)線程被切換回來的時候能夠知道該線程上次運(yùn)行到哪兒了。
需要注意的是罩旋,如果執(zhí)行的是 native 方法啊央,那么程序計數(shù)器記錄的是 undefined 地址,只有執(zhí)行的是 Java 代碼時程序計數(shù)器記錄的才是下一條指令的地址涨醋。
所以瓜饥,程序計數(shù)器私有主要是為了線程切換后能恢復(fù)到正確的執(zhí)行位置。
虛擬機(jī)棧和本地方法棧為什么是私有的?
- 虛擬機(jī)棧: 每個 Java 方法在執(zhí)行的同時會創(chuàng)建一個棧幀用于存儲局部變量表浴骂、操作數(shù)棧乓土、常量池引用等信息。從方法調(diào)用直至執(zhí)行完成的過程靠闭,就對應(yīng)著一個棧幀在 Java 虛擬機(jī)棧中入棧和出棧的過程帐我。
- 本地方法棧: 和虛擬機(jī)棧所發(fā)揮的作用非常相似,區(qū)別是: 虛擬機(jī)棧為虛擬機(jī)執(zhí)行 Java 方法 (也就是字節(jié)碼)服務(wù)愧膀,而本地方法棧則為虛擬機(jī)使用到的 Native 方法服務(wù)拦键。 在 HotSpot 虛擬機(jī)中和 Java 虛擬機(jī)棧合二為一。
所以檩淋,為了保證線程中的局部變量不被別的線程訪問到芬为,虛擬機(jī)棧和本地方法棧是線程私有的。
堆和方法區(qū)
堆和方法區(qū)是所有線程共享的資源蟀悦,其中堆是進(jìn)程中最大的一塊內(nèi)存媚朦,主要用于存放新創(chuàng)建的對象 (所有對象都在這里分配內(nèi)存),方法區(qū)主要用于存放已被加載的類信息日戈、常量询张、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)浙炼。
并發(fā)與并行的區(qū)別
- 并發(fā): 同一時間段份氧,多個任務(wù)都在執(zhí)行 (單位時間內(nèi)不一定同時執(zhí)行);
- 并行: 單位時間內(nèi)弯屈,多個任務(wù)同時執(zhí)行蜗帜。
為什么要使用多線程
先從總體上來說:
- 從計算機(jī)底層來說: 線程可以比作是輕量級的進(jìn)程,是程序執(zhí)行的最小單位,線程間的切換和調(diào)度的成本遠(yuǎn)遠(yuǎn)小于進(jìn)程资厉。另外厅缺,多核 CPU 時代意味著多個線程可以同時運(yùn)行,這減少了線程上下文切換的開銷。
- 從當(dāng)代互聯(lián)網(wǎng)發(fā)展趨勢來說: 現(xiàn)在的系統(tǒng)動不動就要求百萬級甚至千萬級的并發(fā)量湘捎,而多線程并發(fā)編程正是開發(fā)高并發(fā)系統(tǒng)的基礎(chǔ)诀豁,利用好多線程機(jī)制可以大大提高系統(tǒng)整體的并發(fā)能力以及性能。
使用多線程可能帶來什么問題?
并發(fā)編程的目的就是為了能提高程序的執(zhí)行效率提高程序運(yùn)行速度窥妇,但是并發(fā)編程并不總是能提高程序運(yùn)行速度的且叁,而且并發(fā)編程可能會遇到很多問題,比如:內(nèi)存泄漏秩伞、上下文切換逞带、死鎖還有受限于硬件和軟件的資源閑置問題。
線程的生命周期和狀態(tài)
Java 線程在運(yùn)行的生命周期中的指定時刻只可能處于下面 6 種不同狀態(tài)的其中一個狀態(tài)
線程在生命周期中并不是固定處于某一個狀態(tài)而是隨著代碼的執(zhí)行在不同狀態(tài)之間切換纱新。
由上圖可以看出:線程創(chuàng)建之后它將處于 NEW(新建) 狀態(tài)展氓,調(diào)用 start()
方法后開始運(yùn)行,線程這時候處于 READY(可運(yùn)行) 狀態(tài)脸爱∮龉可運(yùn)行狀態(tài)的線程獲得了 CPU 時間片(timeslice)后就處于 RUNNING(運(yùn)行) 狀態(tài)。
操作系統(tǒng)隱藏 Java 虛擬機(jī)(JVM)中的 RUNNABLE 和 RUNNING 狀態(tài)簿废,它只能看到 RUNNABLE 狀態(tài)所以 Java 系統(tǒng)一般將這兩個狀態(tài)統(tǒng)稱為 RUNNABLE(運(yùn)行中) 狀態(tài) 空入。
當(dāng)線程執(zhí)行 wait()
方法之后,線程進(jìn)入 WAITING(等待) 狀態(tài)族檬。進(jìn)入等待狀態(tài)的線程需要依靠其他線程的通知才能夠返回到運(yùn)行狀態(tài)歪赢,而 TIME_WAITING(超時等待) 狀態(tài)相當(dāng)于在等待狀態(tài)的基礎(chǔ)上增加了超時限制,比如通過 sleep(long millis)
方法或 wait(long millis)
方法可以將 Java 線程置于 TIMED WAITING 狀態(tài)单料。當(dāng)超時時間到達(dá)后 Java 線程將會返回到 RUNNABLE 狀態(tài)埋凯。當(dāng)線程調(diào)用同步方法時,在沒有獲取到鎖的情況下扫尖,線程將會進(jìn)入到 BLOCKED(阻塞) 狀態(tài)白对。線程在執(zhí)行 Runnable 的run()
方法之后將會進(jìn)入到 TERMINATED(終止) 狀態(tài)。
什么是上下文切換
多線程編程中一般線程的個數(shù)都大于 CPU 核心的個數(shù)换怖,而一個 CPU 核心在任意時刻只能被一個線程使用甩恼,為了讓這些線程都能得到有效執(zhí)行,CPU 采取的策略是為每個線程分配時間片并輪轉(zhuǎn)的形式沉颂。當(dāng)一個線程的時間片用完的時候就會重新處于就緒狀態(tài)讓給其他線程使用条摸,這個過程就屬于一次上下文切換。
概括來說就是:當(dāng)前任務(wù)在執(zhí)行完 CPU 時間片切換到另一個任務(wù)之前會先保存自己的狀態(tài)兆览,以便下次再切換會這個任務(wù)時屈溉,可以再加載這個任務(wù)的狀態(tài)塞关。任務(wù)從保存到再加載的過程就是一次上下文切換抬探。
什么是線程死鎖?如何避免死鎖?
認(rèn)識線程死鎖
多個線程同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放。由于線程被無限期地阻塞小压,因此程序不可能正常終止线梗。
如下圖所示,線程 A 持有資源 2怠益,線程 B 持有資源 1仪搔,他們同時都想申請對方的資源,所以這兩個線程就會互相等待而進(jìn)入死鎖狀態(tài)蜻牢。
產(chǎn)生死鎖必須具備以下四個條件:
- 互斥條件:該資源任意一個時刻只由一個線程占用烤咧。
- 請求與保持條件:一個進(jìn)程因請求資源而阻塞時,對已獲得的資源保持不放抢呆。
- 不剝奪條件:線程已獲得的資源在末使用完之前不能被其他線程強(qiáng)行剝奪煮嫌,只有自己使用完畢后才釋放資源。
- 循環(huán)等待條件:若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系抱虐。
如何避免線程死鎖
只要破壞產(chǎn)生死鎖的四個條件中的其中一個就可以了昌阿。
破壞互斥條件
這個條件我們沒有辦法破壞,因為我們用鎖本來就是想讓他們互斥的(臨界資源需要互斥訪問)恳邀。
破壞請求與保持條件
一次性申請所有的資源懦冰。
破壞不剝奪條件
占用部分資源的線程進(jìn)一步申請其他資源時,如果申請不到谣沸,可以主動釋放它占有的資源刷钢。
破壞循環(huán)等待條件
靠按序申請資源來預(yù)防。按某一順序申請資源乳附,釋放資源則反序釋放闯捎。破壞循環(huán)等待條件。
sleep() 方法和 wait() 方法區(qū)別和共同點
- 兩者最主要的區(qū)別在于:sleep 方法沒有釋放鎖许溅,而 wait 方法釋放了鎖 瓤鼻。
- 兩者都可以暫停線程的執(zhí)行。
- Wait 通常被用于線程間交互/通信,sleep 通常被用于暫停執(zhí)行。
- wait() 方法被調(diào)用后豪筝,線程不會自動蘇醒礁击,需要別的線程調(diào)用同一個對象上的 notify() 或者 notifyAll() 方法。sleep() 方法執(zhí)行完成后夏漱,線程會自動蘇醒。或者可以使用wait(long timeout)超時后線程會自動蘇醒沃粗。
我們調(diào)用 start() 方法時會執(zhí)行 run() 方法,為什么我們不能直接調(diào)用 run() 方法键畴?
new 一個 Thread最盅,線程進(jìn)入了新建狀態(tài);調(diào)用 start() 方法突雪,會啟動一個線程并使線程進(jìn)入了就緒狀態(tài),當(dāng)分配到時間片后就可以開始運(yùn)行了涡贱。 start() 會執(zhí)行線程的相應(yīng)準(zhǔn)備工作咏删,然后自動執(zhí)行 run() 方法的內(nèi)容,這是真正的多線程工作问词。 而直接執(zhí)行 run() 方法督函,會把 run 方法當(dāng)成一個 main 線程下的普通方法去執(zhí)行,并不會在某個線程中執(zhí)行它激挪,所以這并不是多線程工作辰狡。
總結(jié): 調(diào)用 start 方法方可啟動線程并使線程進(jìn)入就緒狀態(tài),而 run 方法只是 thread 的一個普通方法調(diào)用垄分,還是在主線程里執(zhí)行搓译。
final,static,this,super 關(guān)鍵字總結(jié)
final 關(guān)鍵字
final關(guān)鍵字主要用在三個地方:變量、方法锋喜、類些己。
- 對于一個final變量,如果是基本數(shù)據(jù)類型的變量嘿般,則其數(shù)值一旦在初始化之后便不能更改段标;如果是引用類型的變量,則在對其初始化之后便不能再讓其指向另一個對象炉奴。
- 當(dāng)用final修飾一個類時逼庞,表明這個類不能被繼承。final類中的所有成員方法都會被隱式地指定為final方法瞻赶。
- 使用final方法的原因:把方法鎖定赛糟,以防任何繼承類修改它的含義
static 關(guān)鍵字
static 關(guān)鍵字主要有以下四種使用場景:
- 修飾成員變量和成員方法: 被 static 修飾的成員屬于類,不屬于單個這個類的某個對象砸逊,被類中所有對象共享璧南,靜態(tài)變量 存放在 Java 內(nèi)存區(qū)域的方法區(qū)。靜態(tài)方法不能調(diào)用非靜態(tài)方法和非靜態(tài)成員變量师逸。靜態(tài)變量 存放在 Java 內(nèi)存區(qū)域的方法區(qū)司倚。
- 靜態(tài)代碼塊:代碼執(zhí)行順序(靜態(tài)代碼塊 —>非靜態(tài)代碼 —>構(gòu)造方法) 該類不管創(chuàng)建多少對象,靜態(tài)代碼塊只執(zhí)行一次.
- 靜態(tài)內(nèi)部類(只能修飾內(nèi)部類):它的創(chuàng)建是不需要依賴外圍類的創(chuàng)建篓像。不能使用任何外圍類的非static成員變量和方法动知。
- 靜態(tài)導(dǎo)包
this 關(guān)鍵字
this關(guān)鍵字用于引用類的當(dāng)前實例,代表對本類對象的引用员辩,指向本類對象
super 關(guān)鍵字
super關(guān)鍵字用于從子類訪問父類的變量和方法盒粮, 代表對父類對象的引用,指向父類對象
使用 this 和 super 要注意的問題:
- 在構(gòu)造器中使用 super() 調(diào)用父類中的其他構(gòu)造方法時奠滑,該語句必須處于構(gòu)造器的首行丹皱,否則編譯器會報錯妒穴。另外,this 調(diào)用本類中的其他構(gòu)造方法時种呐,也要放在首行。
- this弃甥、super不能用在static方法中爽室。