單例模式是指一個類,只能創建一個對象實例
本文使用了4種方式實現單例模式並逐一介紹其特點,分別爲餓漢式,懶漢式,雙重檢索式和靜態內部類式。
一、餓漢式
實現餓漢式單例模式分爲三步:
代碼如下:
public class Single1 {
static int i = 1;
static Single1 s = new Single1();
private Single1() {
System.out.println("Single1的構造器");
}
public static Single1 getObject() {
return s;
}
}
public class TestSingle1 {
public static void main(String[] args) {
//此時僅僅反問Single1類的i屬性,都會創建對象
System.out.println(Single1.i);
System.out.println(Single1.getObject());
System.out.println(Single1.getObject());
System.out.println(Single1.getObject());
}
}
結果爲:
創建對象
1
cn.design.single.Single1@6d06d69c
cn.design.single.Single1@6d06d69c
cn.design.single.Single1@6d06d69c
二、懶漢式
實現餓漢式單例模式分爲三步:
public class Single2 {
public static int i = 1;
private static Single2 s;
public static Single2 getObj() {
if (s == null) {
s = new Single2();
}
return s;
}
private Single2() {
System.out.println("Single2的構造器!");
}
}
public class TestSingle2 {
public static void main(String[] args) {
//懶漢式時反問屬性i不會創建對象
System.out.println(Single2.i);
Single2 t1 = Single2.getObj();
Single2 t2 = Single2.getObj();
System.out.println(t1 == t2);
}
}
結果爲:
1
Single2的構造器!
true
懶漢式中對象總是在被需要的時候才創建,解決了對象可能加載時機過早的問題;但是懶漢式的線程是不安全,因爲如果一個線程執行getObj()方法創建一個對象時,另外一個線程也可以該方法創建對象,下面對懶漢式的線程不安全問題進行測試,增加一個類實現Runnable接口
public class GetThread implements Runnable{
public Set<Single2> set = new HashSet<>();
@Override
public void run() {
// TODO Auto-generated method stub
Single2 s = Single2.getObj();
set.add(s);
System.out.println(set);
}
}
使用Set集合對創建的s對象進行添加,如果set中能夠添加進多個元素,就說明創建了多個Single2的對象。
測試類:
public class TestThread {
public static void main(String[] args) {
GetThread gt = new GetThread();
new Thread(gt).start();
new Thread(gt).start();
new Thread(gt).start();
}
}
測試結果:Single2的構造器!
Single2的構造器!
Single2的構造器!
[cn.design.TestSingleThread.Single2@6019722a, cn.design.TestSingleThread.Single2@4de9fa9c, cn.design.TestSingleThread.Single2@2b63218b]
[cn.design.TestSingleThread.Single2@6019722a, cn.design.TestSingleThread.Single2@4de9fa9c, cn.design.TestSingleThread.Single2@2b63218b]
[cn.design.TestSingleThread.Single2@6019722a, cn.design.TestSingleThread.Single2@4de9fa9c, cn.design.TestSingleThread.Single2@2b63218b]
可以看到set中存儲了由三個線程分別創建的Single2對象,所以懶漢式在多線程的情況下是不安全的
三、雙重檢索式
雙重檢索式在懶漢式的基礎上進行了改進,添加了synchronized同步代碼塊
public class Single3 {
public static int i;
private static Single3 s;
private Single3(){
System.out.println("Single3的構造器!");
}
public static Single3 getObj(){
if(s == null){
synchronized (Single3.class) {
if(s == null){
s = new Single3();
}
}
}
return s;
}
}
public class Single3Thread implements Runnable {
Set<Single3> set = new HashSet<>();
@Override
public void run() {
// TODO Auto-generated method stub
Single3 s = Single3.getObj();
set.add(s);
System.out.println(set);
}
}
public class TestSingle3 {
public static void main(String[] args) {
Single3Thread st = new Single3Thread();
System.out.println(Single3.i);
new Thread(st).start();
new Thread(st).start();
new Thread(st).start();
new Thread(st).start();
new Thread(st).start();
}
}
運行結果:
0
Single3的構造器!
[cn.design.single.Single3@272fd899]
[cn.design.single.Single3@272fd899]
[cn.design.single.Single3@272fd899]
[cn.design.single.Single3@272fd899]
[cn.design.single.Single3@272fd899]
可以看出雙重檢鎖式是線程安全的,而且不存在餓漢式中對象創建過早的問題;但是雙重檢鎖式並不能保證它會在單處理器或多處理器計算機上順利運行,雙重檢查鎖定失敗的問題並不歸咎於 JVM 中的實現 bug,而是歸咎於 Java 平臺內存模型。
四、靜態內部類式
1、定義一個靜態內部類2、外部類構造器私有化
3、外部類的對象作爲內部類的屬性存在,static修飾,進行初始化
4、公共的靜態的方法
public class Single4 {
public static int i = 1;
static class InnerClass{
private static Single4 s = new Single4();
public static Single4 getObj(){
return s;
}
}
public Single4(){
System.out.println("Single4的構造器");
}
}
public class Single4Thread implements Runnable{
Set<Single4> set = new HashSet<>();
@Override
public void run() {
// TODO Auto-generated method stub
Single4 s = Single4.InnerClass.getObj();
set.add(s);
System.out.println(set);
}
}
public class TestSingle4 {
public static void main(String[] args) {
System.out.println(Single4.i);
System.out.println(Single4.InnerClass.getObj());
System.out.println(Single4.InnerClass.getObj());
System.out.println(Single4.InnerClass.getObj());
System.out.println(Single4.InnerClass.getObj());
System.out.println(Single4.InnerClass.getObj());
Single4Thread st = new Single4Thread();
new Thread(st).start();
new Thread(st).start();
new Thread(st).start();
new Thread(st).start();
new Thread(st).start();
}
}
測試結果:
1
Single4的構造器
cn.design.single.Single4@6d06d69c
cn.design.single.Single4@6d06d69c
cn.design.single.Single4@6d06d69c
cn.design.single.Single4@6d06d69c
cn.design.single.Single4@6d06d69c
[cn.design.single.Single4@6d06d69c]
[cn.design.single.Single4@6d06d69c]
[cn.design.single.Single4@6d06d69c]
[cn.design.single.Single4@6d06d69c]
[cn.design.single.Single4@6d06d69c]
可以看出靜態內部類式也是線程安全的,而且不存在餓漢式中對象創建過早的問題,靜態內部類可以訪問其外部類的成員屬性和方法,同時,靜態內部類只有當被調用的時候纔開始首次被加載,因此推薦使用靜態內部類的方法實現單例模式。