概念:
java中單例模式是一種常見的設計模式,單例模式分三種:懶漢式單例、餓漢式單例、登記式單例三種。
單例模式有一下特點:
1、單例類只能有一個實例。
2、單例類必須自己創(chuàng)建自己的唯一實例。
3、單例類必須給所有其他對象提供這一實例。
單例模式確保某個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例。在計算機系統(tǒng)中,線程池、緩存、日志對象、對話框、打印機、顯卡的驅動程序對象常被設計成單例。這些應用都或多或少具有資源管理器的功能。每臺計算機可以有若干個打印機,但只能有一個Printer Spooler,以避免兩個打印作業(yè)同時輸出到打印機中。每臺計算機可以有若干通信端口,系統(tǒng)應當集中管理這些通信端口,以避免一個通信端口同時被兩個請求同時調(diào)用。總之,選擇單例模式就是為了避免不一致狀態(tài),避免政出多頭。
這里主要詳細介紹兩種:懶漢式和餓漢式
一、立即加載/餓漢式
在調(diào)用方法前,實例就已經(jīng)被創(chuàng)建,代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package com.weishiyao.learn.day8.singleton.ep1; public class MyObject { // 立即加載方式==惡漢模式 private static MyObject myObject = new MyObject(); private MyObject() { } public static MyObject getInstance() { // 此代碼版本為立即加載 // 此版本代碼的缺點是不能有其他實例變量 // 因為getInstance()方法沒有同步 // 所以有可能出現(xiàn)非線程安全的問題 return myObject; } } |
創(chuàng)建線程類
1
2
3
4
5
6
7
8
|
package com.weishiyao.learn.day8.singleton.ep1; public class MyThread extends Thread { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } } |
創(chuàng)建運行類
1
2
3
4
5
6
7
8
9
10
11
12
|
package com.weishiyao.learn.day8.singleton.ep1; public class Run { public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); MyThread t3 = new MyThread(); t1.start(); t2.start(); t3.start(); } } |
運行結果
167772895
167772895
167772895
hashCode是同一個值,說明對象也是同一個,說明實現(xiàn)了立即加載型的單利模式
二、延遲加載/懶漢式
在調(diào)用方法以后實例才會被創(chuàng)建,實現(xiàn)方案可以是將實例化放到無參構造函數(shù)當中,這樣只有當調(diào)用的時候才會創(chuàng)建對象的實例,代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package com.weishiyao.learn.day8.singleton.ep2; public class MyObject { private static MyObject myObject; private MyObject() { } public static MyObject getInstance() { // 延遲加載 if (myObject != null ) { } else { myObject = new MyObject(); } return myObject; } } |
創(chuàng)建線程類
1
2
3
4
5
6
7
8
|
package com.weishiyao.learn.day8.singleton.ep2; public class MyThread extends Thread { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } } |
創(chuàng)建運行類
1
2
3
4
5
6
7
8
|
package com.weishiyao.learn.day8.singleton.ep2; public class Run { public static void main(String[] args) { MyThread t1 = new MyThread(); t1.start(); } } |
運行結果
167772895
這樣雖然取出了一個對象的實例,但是如果在多線程的環(huán)境中,就會出現(xiàn)多個實例的情況,這樣就不是單例模式了
運行測試類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package com.weishiyao.learn.day8.singleton.ep2; public class Run { public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); MyThread t3 = new MyThread(); MyThread t4 = new MyThread(); MyThread t5 = new MyThread(); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); } } |
運行結果
980258163
1224717057
1851889404
188820504
1672864109
既然出現(xiàn)問題,就要解決問題,在懶漢模式中的多線程的解決方案,代碼:
第一種方案,最常見的,加synchronized,而synchronized可以加到不同的位置
第一種,方法鎖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
package com.weishiyao.learn.day8.singleton.ep3; public class MyObject { private static MyObject myObject; private MyObject() { } synchronized public static MyObject getInstance() { // 延遲加載 try { if (myObject != null ) { } else { // 模擬在創(chuàng)建對象之前做一些準備性的工作 Thread.sleep( 2000 ); myObject = new MyObject(); } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } } |
這種synchronized的同步方案導致效率過于低下,整個方法都被鎖住
第二種synchronized使用方案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package com.weishiyao.learn.day8.singleton.ep3; public class MyObject { private static MyObject myObject; private MyObject() { } public static MyObject getInstance() { // 延遲加載 try { synchronized (MyObject. class ) { if (myObject != null ) { } else { // 模擬在創(chuàng)建對象之前做一些準備性的工作 Thread.sleep( 2000 ); myObject = new MyObject(); } } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } } |
這種方法效率一樣很低,方法內(nèi)的所有代碼都被鎖住,只需要鎖住關鍵代碼就好,第三種synchronized使用方案
package com.weishiyao.learn.day8.singleton.ep3;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public class MyObject { private static MyObject myObject; private MyObject() { } public static MyObject getInstance() { // 延遲加載 try { if (myObject != null ) { } else { // 模擬在創(chuàng)建對象之前做一些準備性的工作 Thread.sleep( 2000 ); synchronized (MyObject. class ) { myObject = new MyObject(); } } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } } |
這么寫看似是最優(yōu)方案了,但是,運行一下結果,發(fā)現(xiàn),其實它是非線程安全的
結果:
1224717057
971173439
1851889404
1224717057
1672864109
Why?
雖然鎖住了對象創(chuàng)建的語句,每次只能有一個線程完成創(chuàng)建,但是,當?shù)谝粋€線程進來創(chuàng)建完成Object對象以后,第二個線程進來還是可以繼續(xù)創(chuàng)建的,因為我們緊緊只鎖住了創(chuàng)建語句,這個問題解決方案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
package com.weishiyao.learn.day8.singleton.ep3; public class MyObject { private static MyObject myObject; private MyObject() { } public static MyObject getInstance() { // 延遲加載 try { if (myObject != null ) { } else { // 模擬在創(chuàng)建對象之前做一些準備性的工作 Thread.sleep( 2000 ); synchronized (MyObject. class ) { if (myObject == null ) { myObject = new MyObject(); } } } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } } |
只需要在鎖里面再添加一個判斷,就可以保證單例了,這個是DCL雙檢查機制
結果如下:
1224717057
1224717057
1224717057
1224717057
1224717057
三、使用內(nèi)置靜態(tài)類實現(xiàn)單例
主要代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package com.weishiyao.learn.day8.singleton.ep4; public class MyObject { // 內(nèi)部類方式 private static class MyObjectHandler { private static MyObject myObject = new MyObject(); } public MyObject() { } public static MyObject getInstance() { return MyObjectHandler.myObject; } } |
線程類代碼
1
2
3
4
5
6
7
8
|
package com.weishiyao.learn.day8.singleton.ep4; public class MyThread extends Thread { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } } |
運行類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package com.weishiyao.learn.day8.singleton.ep4; public class Run { public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); MyThread t3 = new MyThread(); MyThread t4 = new MyThread(); MyThread t5 = new MyThread(); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); } } |
結果
1851889404
1851889404
1851889404
1851889404
1851889404
通過內(nèi)部靜態(tài)類,得到了線程安全的單例模式
四、序列化和反序列化單例模式
內(nèi)置靜態(tài)類可以達到線程安全的問題,但如果遇到序列化對象時,使用默認方式得到的結果還是多例的
MyObject代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
package com.weishiyao.learn.day8.singleton.ep5; import java.io.Serializable; public class MyObject implements Serializable { /** * */ private static final long serialVersionUID = 888L; // 內(nèi)部類方式 private static class MyObjectHandler { private static MyObject myObject = new MyObject(); } public MyObject() { } public static MyObject getInstance() { return MyObjectHandler.myObject; } // protected MyObject readResolve() { // System.out.println("調(diào)用了readResolve方法!"); // return MyObjectHandler.myObject; // } } |
業(yè)務類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
package com.weishiyao.learn.day8.singleton.ep5; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class SaveAndRead { public static void main(String[] args) { try { MyObject myObject = MyObject.getInstance(); FileOutputStream fosRef = new FileOutputStream( new File( "myObjectFile.txt" )); ObjectOutputStream oosRef = new ObjectOutputStream(fosRef); oosRef.writeObject(myObject); oosRef.close(); fosRef.close(); System.out.println(myObject.hashCode()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } FileInputStream fisRef; try { fisRef = new FileInputStream( new File( "myObjectFile.txt" )); ObjectInputStream iosRef = new ObjectInputStream(fisRef); MyObject myObject = (MyObject) iosRef.readObject(); iosRef.close(); fisRef.close(); System.out.println(myObject.hashCode()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } |
結果
970928725
1099149023
兩個不同的hashCode,證明并不是同一個對象,解決方案,添加下面這段代碼
1
2
3
4
|
protected MyObject readResolve() { System.out.println( "調(diào)用了readResolve方法!" ); return MyObjectHandler.myObject; } |
在反序列化的時候調(diào)用,可以得到同一個對象
System.out.println(myObject.readResolve().hashCode());
結果
1255301379
調(diào)用了readResolve方法!
1255301379
相同的hashCode,證明得到了同一個對象
五、使用static代碼塊實現(xiàn)單例
靜態(tài)代碼塊中的代碼在使用類的時候就已經(jīng)執(zhí)行了,所以可以應用靜態(tài)代碼快這個特性來實現(xiàn)單利模式
MyObject類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package com.weishiyao.learn.day8.singleton.ep6; public class MyObject { private static MyObject instance = null ; private MyObject() { super (); } static { instance = new MyObject(); } public static MyObject getInstance() { return instance; } } |
線程類
1
2
3
4
5
6
7
8
9
10
|
package com.weishiyao.learn.day8.singleton.ep6; public class MyThread extends Thread { @Override public void run() { for ( int i = 0 ; i < 5 ; i++) { System.out.println(MyObject.getInstance().hashCode()); } } } |
運行類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package com.weishiyao.learn.day8.singleton.ep6; public class Run { public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); MyThread t3 = new MyThread(); MyThread t4 = new MyThread(); MyThread t5 = new MyThread(); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); } } |
運行結果:
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
通過靜態(tài)代碼塊只執(zhí)行一次的特性也成功的得到了線程安全的單例模式
六、使用enum枚舉數(shù)據(jù)類型實現(xiàn)單例模式
枚舉enum和靜態(tài)代碼塊的特性類似,在使用枚舉時,構造方法會被自動調(diào)用,也可以用來實現(xiàn)單例模式
MyObject類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
package com.weishiyao.learn.day8.singleton.ep7; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public enum MyObject { connectionFactory; private Connection connection; private MyObject() { try { System.out.println( "調(diào)用了MyObject的構造" ); String url = "jdbc:mysql://172.16.221.19:3306/wechat_1?useUnicode=true&characterEncoding=UTF-8" ; String name = "root" ; String password = "111111" ; String driverName = "com.mysql.jdbc.Driver" ; Class.forName(driverName); connection = DriverManager.getConnection(url, name, password); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } public Connection getConnection() { return connection; } } |
線程類
1
2
3
4
5
6
7
8
9
10
|
package com.weishiyao.learn.day8.singleton.ep7; public class MyThread extends Thread { @Override public void run() { for ( int i = 0 ; i < 5 ; i++) { System.out.println(MyObject.connectionFactory.getConnection().hashCode()); } } } |
運行類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package com.weishiyao.learn.day8.singleton.ep7; public class Run { public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); MyThread t3 = new MyThread(); MyThread t4 = new MyThread(); MyThread t5 = new MyThread(); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); } } |
運行結果
調(diào)用了MyObject的構造
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
上面這種寫法將枚舉類暴露了,違反了“職責單一原則”,可以使用一個類將枚舉包裹起來
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
package com.weishiyao.learn.day8.singleton.ep8; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class MyObject { public enum MyEnumSingleton { connectionFactory; private Connection connection; private MyEnumSingleton() { try { System.out.println( "調(diào)用了MyObject的構造" ); String url = "jdbc:mysql://172.16.221.19:3306/wechat_1?useUnicode=true&characterEncoding=UTF-8" ; String name = "root" ; String password = "111111" ; String driverName = "com.mysql.jdbc.Driver" ; Class.forName(driverName); connection = DriverManager.getConnection(url, name, password); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } public Connection getConnection() { return connection; } } public static Connection getConnection() { return MyEnumSingleton.connectionFactory.getConnection(); } } |
更改線程代碼
1
2
3
4
5
6
7
8
9
10
|
package com.weishiyao.learn.day8.singleton.ep8; public class MyThread extends Thread { @Override public void run() { for ( int i = 0 ; i < 5 ; i++) { System.out.println(MyObject.getConnection().hashCode()); } } } |
結果
調(diào)用了MyObject的構造
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
以上總結了單利模式與多線程結合時遇到的各種情況和解決方案,以供以后使用時查閱。