概念:
java中單例模式是一種常見的設計模式,單例模式分為三種:懶漢式單例、餓漢式單例、登記式單例三種。
單例模式有一下特點:
1、單例類別只能有一個實例。
2、單例類別必須自行創建自己的唯一實例。
3、單例類別必須提供此實例給所有其他物件。
單例模式確保某個類別只有一個實例,而且自行實例化並向整個系統提供這個實例。在電腦系統中,執行緒池、快取、日誌物件、對話方塊、印表機、顯示卡的驅動程式物件常被設計成單例。這些應用都或多或少具有資源管理器的功能。每台電腦可以有若干台印表機,但只能有一個Printer Spooler,以避免兩個列印作業同時輸出到印表機。每台電腦可以有若干通信端口,系統應集中管理這些通信端口,以避免一個通信端口同時被兩個請求同時調用。總之,選擇單例模式就是為了避免不一致狀態,避免政出多頭。
這裡主要詳細介紹兩種:懶漢式和餓漢式
一、立即載入/餓漢式
在呼叫方法前,實例就已經被創建,程式碼:
package com.weishiyao.learn.day8.singleton.ep1; public class MyObject { // 立即加载方式==恶汉模式 private static MyObject myObject = new MyObject(); private MyObject() { } public static MyObject getInstance() { // 此代码版本为立即加载 // 此版本代码的缺点是不能有其他实例变量 // 因为getInstance()方法没有同步 // 所以有可能出现非线程安全的问题 return myObject; } }
創建執行緒類
reeereee
運行類別package com.weishiyao.learn.day8.singleton.ep1; public class MyThread extends Thread { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }
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(); } }
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; } }
package com.weishiyao.learn.day8.singleton.ep2; public class MyThread extends Thread { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }
package com.weishiyao.learn.day8.singleton.ep2; public class Run { public static void main(String[] args) { MyThread t1 = new MyThread(); t1.start(); } }
1224717057
1851889404188820504
1672864109
188820504
1672864109
188820504
1672864109
1672864109
1888205041672864109
1888205041672864109
1888205041672864109
1888205041672864109
synchronized ,而synchronized可以加到不同的位置
第一種,方法鎖
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(); } }
這種synchronized的同步方案導致效率過於低下,整個方法都被鎖住
第二種synchronized使用方案
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 { // 模拟在创建对象之前做一些准备性的工作 Thread.sleep(2000); myObject = new MyObject(); } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } }
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 { // 模拟在创建对象之前做一些准备性的工作 Thread.sleep(2000); myObject = new MyObject(); } } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } }
971173439
1851889404717057971173439
1851889404717057971173439
18518894047012247175
雖然鎖住了物件創建的語句,每次只能有一個線程完成創建,但是,當第一個線程進來創建完成Object物件以後,第二個線程進來還是可以繼續創建的,因為我們緊緊只鎖定了創建語句,這個問題解決方案
public class MyObject { private static MyObject myObject; private MyObject() { } public static MyObject getInstance() { // 延迟加载 try { if (myObject != null) { } else { // 模拟在创建对象之前做一些准备性的工作 Thread.sleep(2000); synchronized (MyObject.class) { myObject = new MyObject(); } } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } }
結果如下:
1224717057
12247170571247170570 12247170571224717057
三、使用內建靜態類別實作單例主要程式碼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 { // 模拟在创建对象之前做一些准备性的工作 Thread.sleep(2000); synchronized (MyObject.class) { if (myObject == null) { myObject = new MyObject(); } } } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } }
package com.weishiyao.learn.day8.singleton.ep4; public class MyObject { // 内部类方式 private static class MyObjectHandler { private static MyObject myObject = new MyObject(); } public MyObject() { } public static MyObject getInstance() { return MyObjectHandler.myObject; } }
package com.weishiyao.learn.day8.singleton.ep4; public class MyThread extends Thread { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }
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透過內部靜態類,得到了執行緒安全的單例模式
四、序列化和反序列化單例模式
內建靜態類別可以達到執行緒安全的問題,但如果遇到序列化物件時,使用預設方式得到的結果還是多例的
MyObject代碼
package com.weishiyao.learn.day8.singleton.ep5; import java.io.Serializable; public class MyObject implements Serializable { /** * */ private static final long serialVersionUID = 888L; // 内部类方式 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("调用了readResolve方法!"); // return MyObjectHandler.myObject; // } }
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
System.out.println(myObject.readResolve().hashCode());
結果
調用了readResolve方法!
1255301379相同的hashCode,證明得到了同一個物件
五、使用static程式碼區塊實作單例
靜態程式碼區塊中的程式碼在使用類別的時候就已經執行了,所以可以應用靜態程式碼快這個特性來實作單利模式
MyObject類別
protected MyObject readResolve() { System.out.println("调用了readResolve方法!"); return MyObjectHandler.myObject; }
執行緒類別
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; } }
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()); } } }
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
通过静态代码块只执行一次的特性也成功的得到了线程安全的单例模式
六、使用enum枚举数据类型实现单例模式
枚举enum和静态代码块的特性类似,在使用枚举时,构造方法会被自动调用,也可以用来实现单例模式
MyObject类
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("调用了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; } }
线程类
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()); } } }
运行类
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(); } }
运行结果
调用了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
上面这种写法将枚举类暴露了,违反了“职责单一原则”,可以使用一个类将枚举包裹起来
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("调用了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(); } }
更改线程代码
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()); } } }
结果
调用了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
以上总结了单利模式与多线程结合时遇到的各种情况和解决方案,以供以后使用时查阅。
更多java多线程之线程安全的单例模式相关文章请关注PHP中文网!