字符串
- java字符串使用+進行拼接時,編譯器會通過StringBuilder進行優化。但是在循環體中使用+=的時候會創建多個StringBuilder,可以在循環體外顯式使用StringBuilder,可以使得程序效率更高
- Buffer是線程安全的,所以在單線程的情況先執行效率低于Builder
單例的寫法
單例一般有兩種寫法,也將其稱為惡漢式和懶漢式,我這里說的是在沒有 lazy load 等考慮的情況下
沒有帶參數的寫法:
public class Singleton {
public static final Singleton INSTANCE = new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return INSTANCE;
}
}
帶參數的寫法:
public class Singleton {
private volatile static Singleton sInstance;
private static final Object LOCK = new Object();
private Singleton(Param param){
// do some init
}
public static Singleton getInstance(Param param) {
if (sInstance == null) {
synchronized (LOCK) {
if (sInstance == null) {
sInstance = new Singleton(param);
}
}
}
return sInstance;
}
}
關于為何需要雙重判空網上有很多介紹的文章,這里不再多說,需要注意的是需要使用volatile防止重排序問題
this逃逸
this逃逸主要指在構造方法沒有執行完畢的情況下,其他線程獲取到了這個對象的引用。需要小心在構造方法中不要使用一些異步手段將this對象發布出去。
并發 多線程相關
- 使用 synchronized 關鍵字同步對象的時候應該在使用對象的地方都使用 synchronized來包裹,否則沒有同步效果
- 將不可變的對象用 final 來修飾可以減少很多同步的問題。final并不表示對象的內容不會變化,只是說不能再賦值。相當于一個指針指向的地址確定了,但是內容沒有限制
- 正確的中斷線程:
class MyThread extends Thread {
public void run() {
try {
doSomeThing();
} catch (InterruptedException e) {
doCancel();
}
}
public void cancel() {
interrupt();
}
}
還有一種方法是通過在 cancel
方法中設置標志位來取消線程也是可以的,注意將標志位設置為 volatile
并且正確的處理中斷
- 線程池的設置:線程池有三種,計算密集線程池、IO線程池,newThread(考慮到有可能希望某個任務及早執行而不是在線程池中排隊)。其中計算密集型線程池的大小建議為$N(cpu核數)+1$,具體參見 《Java并發編程實戰》 8.2 設置線程池大小
- 線程如果在處理不可打斷的任務,應該在出現中斷的時候將中斷狀態保存起來,然后在任務執行完了之后,再響應中斷
class MyThread extends Thread {
private volatile boolean interrupted = false;
public void run() {
try {
while (true) {
try {
doSomeThing();
} catch (InterruptedException e) {
interrupted = true;
}
}
} finally {
if (interrupted)
Thread.currentThread().interrupted();
}
}
public void cancel() {
interrupted = true;
}
}
- Thread#join() 方法可以讓當前線程等待目標線程執行結束,如果目標線程已經結束,則執行當前線程后面的代碼,否則必須等待目標線程結束之后(如果設置了超時時間,超時和目標線程結束兩個條件滿足一個即可)能執行后面的代碼。使用join()方法解決定時任務提前結束引起的一些問題,具體參見 《Java并發編程實戰》 7.1.4 示例:計時運行
static
- 使用private static編譯器會優先考慮將方法內聯
- 無論實例化幾次或者調用幾次方法,static代碼段都執行一次...比如,你創建了類的兩個實例,但是只有創建第一個實例的時候static代碼段才被執行.創建第二個的時候將不執行...使用類名調用靜態方法也同理
- 在實例化一個對象的時候也被調用.而且先于構造函數被調用.也就是說,我們創建一個對象,那么首先先調用static代碼段,然后再調用構造函數
強引用、弱引用、軟引用、虛引用
強引用
強引用是使用最普遍的引用。當內存空間不足,Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足的問題
Object o=new Object(); // 強引用
軟引用
如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。
String str=new String("abc"); // 強引用
SoftReference<String> softRef=new SoftReference<String>(str); // 軟引用
弱引用
弱引用與軟引用的區別在于:只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由于垃圾回收器是一個優先級很低的線程,因此不一定會很快發現那些只具有弱引用的對象。
String str=new String("abc");
WeakReference<String> abcWeakRef = new WeakReference<String>(str);
str=null;
虛引用
虛引用顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。
幾種引用方式在垃圾回收時候的區別
Java中父類與子類中靜態代碼段、非靜態代碼段和構造方法的執行過程
執行流程為:
父類靜態代碼段->子類靜態代碼段->父類非靜態代碼段->父類構造方法->子類非靜態代碼段->父類構造方法
在子類的構造方法執行前必然會執行父類的構造方法,如果指明了super(params)
,則執行父類中對應的構造方法,否則執行默認沒有參數的構造方法
參考: