單例模式應該算是我們經常遇到的一種模式,比如說線程池就是應用了單例,Spring bean也是運用了單例,單例模式節省了整個系統內存的開銷。下面代碼基於線程安全的前提下。
1、餓漢式。類加載時就生成了相應的對象。
public class Singleton1 {
private static Singleton1 instance = new Singleton1();
private Singleton1(){}
public static Singleton1 getInstance(){
return instance;
}
}
2、懶漢式。在第一次調用方法時才生成對象。
public class Singleton2 {
private static Singleton2 instance;
private Singleton2(){}
public static synchronized Singleton2 getInstance(){
if(instance == null){
instance = new Singleton2();
}
return instance;
}
}
3、雙重檢查式。兩次判斷,將同步放到方法內部,但是由於java內存模型,這個可能會出現問題。
public class Singleton3 {
private static Singleton3 instance;
private Singleton3(){}
public static Singleton3 getInstance(){
if(instance == null){
synchronized (Singleton3.class){
if(instance == null){
instance = new Singleton3();
}
}
}
return instance;
}
}
4、靜態內部類。延遲加載,天然的線程安全。
public class Singleton4 {
private static final class InstanceClass{
private static Singleton4 instance = new Singleton4();
}
private Singleton4(){
if(InstanceClass.instance != null){
throw new RuntimeException();
}
}
public static Singleton4 getInstance(){
return InstanceClass.instance;
}
}
5、枚舉式。jvm保證其線程安全性,效率也高。
public enum Singleton5 {
INSTANCE;
public void getInstance(){}
}
上面一共是五種單例的形式,每一種都有其對應的特點。總的來說,如果想要延遲加載,最好選用靜態內部類,如果不需要,枚舉較好。下面是對這五種模式創建對象效率的一個模擬測試,分別調用幾種模式每次生成10萬個對象。這裏採用了countDownLatch類作爲多線程環境下的計數器,CountDownLatch是一個同步工具類,它允許一個或多個線程一直等待,直到其他線程的操作執行完後再執行。
public class SingleTest {
public static void main(String[] args) throws Exception {
Long startTime = System.currentTimeMillis();
int ThreadNum = 10;
final CountDownLatch countDownLatch = new CountDownLatch(ThreadNum);
for (int i = 0; i < ThreadNum; i++) {
new Thread(() -> {
for (int j = 0; j < 1000000; j++) {
Singleton1 s = Singleton1.getInstance();
//Singleton5 s = Singleton5.INSTANCE;
}
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
Long endTime =System.currentTimeMillis();
System.out.println("耗時爲:" + (endTime - startTime));
}
}
最後運行發現,除了懶漢式生成效率較低之外,其他幾種模式差距都很小。