在單例模式與多線程(一)中,介紹了四種在多線程環境下實現的單例模式:餓漢模式、懶漢模式、靜態內部類實現方式和static代碼塊實現方式。下面介紹使用enum枚舉數據類型實現單例和使用序列化與反序列化實現單例。
下面先介紹使用enum枚舉數據類型實現單例:
枚舉enum和靜態代碼塊的特性相似,在使用枚舉類時,構造方法會被自動調用,因此可以應用這個特性實現單例設計模式。
具體示例如下:
package com.javapatterns.singleton;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* 使用enum枚舉數據類型實現單例
*/
public class Test {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(myRunnable);
Thread t3 = new Thread(myRunnable);
t1.start();
t2.start();
t3.start();
}
}
class MyObject {
//將枚舉類作爲MyObject的內部類,這樣可以避免因枚舉類暴露而違反"職責單一原則"
public enum MyEnumSingleton{
connectionFactory;
//提供一個Connection類型的變量
private Connection connection;
//構造方法私有化
private MyEnumSingleton() {
try {
System.out.println("創建MyObject對象");
String driverName = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/mydb";
String name = "root";
String password = "";
Class.forName(driverName);
connection = DriverManager.getConnection(url, name, password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
//提供一個公有的獲取Connection類型的方法
public Connection getConnection() {
return connection;
}
}
//提供一個公有的靜態的獲取Connection類型的方法,在該方法中通過枚舉類中的變量connectionFactory調用公有的獲取Connection類型的方法
public static Connection getConnection()
{
return MyEnumSingleton.connectionFactory.getConnection();
}
}
/*
* 通過實現Runnable接口實現多線程
*/
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(MyObject.getConnection().hashCode());
}
}
運行結果如圖 4-1所示
圖 4-1控制檯打印的hasCode是同一個值,說明對象是同一個,也就說明使用enum枚舉數據類型實現了單例。
接着介紹序列化與反序列化的單例模式實現:
靜態內部類可以達到線程安全問題,但如果遇到序列化對象時,使用默認的方式運行得到的結果還是多例的。
下面進行演示:
package com.javapatterns.singleton;
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;
import java.io.Serializable;
public class SaveAndRead {
public static void main(String[] args) {
try {
MyObject mObject = MyObject.getInstance();
FileOutputStream fosRef = new FileOutputStream(new File("myObjectFile.txt"));
ObjectOutputStream oosRef = new ObjectOutputStream(fosRef);
oosRef.writeObject(mObject);
oosRef.close();
fosRef.close();
System.out.println(mObject.hashCode());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
FileInputStream fisRef = new FileInputStream(new File("myObjectFile.txt"));
ObjectInputStream iosRef = new ObjectInputStream(fisRef);
MyObject mObject = (MyObject) iosRef.readObject();
iosRef.close();
fisRef.close();
System.out.println(mObject.hashCode());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class MyObject implements Serializable {
private static final long serialVersionUID = 888L;
private static class MyObjectHandler {
private static final MyObject myObject = new MyObject();
}
private MyObject() {
}
public static MyObject getInstance() {
return MyObjectHandler.myObject;
}
// protected Object readResolve()
// {
// System.out.println("調用了readResolve方法!");
// return MyObjectHandler.myObject;
//
// }
}
運行結果如圖 4-2所示
圖 4-2 不是同一個對象
解決辦法就是在反序列化中使用readResolve()方法。
去掉如下代碼的註釋:
protected Object readResolve() {
System.out.println("調用了readResolve方法!");
return MyObjectHandler.myObject;
}
去掉後運行結果如圖 4-3所示
圖 4-3 是同一個對象
(正在學習高洪巖先生著的《java多線程編程核心技術》,例子摘自此書,有興趣的可以查閱此書)