在单例模式与多线程(一)中,介绍了四种在多线程环境下实现的单例模式:饿汉模式、懒汉模式、静态内部类实现方式和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多线程编程核心技术》,例子摘自此书,有兴趣的可以查阅此书)