一、源码应用
事实上,我们在JDK或者其他的通用框架中很少能看到标准的单例设计模式,这也就
意味着他确实很经典,但严格的单例设计确实有它的问题和局限性,我们先看看在源
码中的一些案例
1、jdk中的单例
jdk中有一个类的实现是一个标准单例模式(饿汉式),即Runtime类,该类封装了运行时的环境。每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。 一般不能实例化一个Runtime对象,应用程序也不能创建自己的 Runtime类实例,可以通过 getRuntime 方法获取当前Runtime运行时对象的引用。
java
public class Runtime {
private static final Runtime currentRuntime = new Runtime();
private static Version version;
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class {@code Runtime} are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the {@code Runtime} object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
...
}
2、MyBatis中的单例
Mybaits中的org.apache.ibatis.io.VFS使用到了单例模式。VFS就是Virtual File
System的意思,mybatis通过VFS来查找指定路径下的资源。查看VFS以及它的实现
类,不难发现,VFS的角色就是对更"底层"的查找指定资源的方法的封装,将复杂的
"底层"操作封装到易于使用的高层模块中,方便使用者使用。
java
public class public abstract class VFS {
// 使用了内部类
private static class VFSHolder {
static final VFS INSTANCE = createVFS();
@SuppressWarnings("unchecked")
static VFS createVFS() {
// ...省略创建过程
return vfs;
}
}
public static VFS getInstance() {
return VFSHolder.INSTANCE;
}
// ...
}
二、安全问题
1、反射入侵
我们可以通过反射获取私有构造器进行构造,如下代码:
java
@Slf4j
public class TestReflectSingleton {
private static volatile TestReflectSingleton instance;
private TestReflectSingleton(){}
public static TestReflectSingleton getInstance(){
if(instance == null){
synchronized (TestReflectSingleton.class){
if(instance == null){
instance = new TestReflectSingleton();
}
}
}
return instance;
}
/**
* 测试反射入侵
* @param args
*/
public static void main(String[] args) {
Class<TestReflectSingleton> cls = TestReflectSingleton.class;
try {
Constructor<TestReflectSingleton> constructor = cls.getDeclaredConstructor();
// 设置为可见
constructor.setAccessible(true);
TestReflectSingleton instance1 = TestReflectSingleton.getInstance();
TestReflectSingleton instance2 = constructor.newInstance();
boolean flag = instance2 == instance1;
log.info("flag -> {}", flag);
log.info("flag -> {}", flag);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}

这样输出的结果是false,证明对象不是同一个对象,我们可以在构造方法中再加一个判断对象是否为空的条件即可。代码如下:
java
@Slf4j
public class TestReflectSingleton {
private static volatile TestReflectSingleton instance;
private TestReflectSingleton(){
if(instance != null){
// 实例化直接抛出异常
throw new RuntimeException("实例:【" + this.getClass().getName() + "】已经存在,该实例只被实例化一次!");
}
}
public static TestReflectSingleton getInstance(){
if(instance == null){
synchronized (TestReflectSingleton.class){
if(instance == null){
instance = new TestReflectSingleton();
}
}
}
return instance;
}
/**
* 测试反射入侵
* @param args
*/
public static void main(String[] args) {
Class<TestReflectSingleton> cls = TestReflectSingleton.class;
try {
Constructor<TestReflectSingleton> constructor = cls.getDeclaredConstructor();
constructor.setAccessible(true);
TestReflectSingleton instance1 = TestReflectSingleton.getInstance();
TestReflectSingleton instance2 = constructor.newInstance();
boolean flag = instance2 == instance1;
log.info("flag -> {}", flag);
log.info("flag -> {}", flag);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
直接抛出异常,这样就可以防止反射入侵啦

2、序列化与反序列化问题
java
@Slf4j
public class TestSerializeSingleton implements Serializable {
/**
* 简单的写个懒加载吧
*/
private static TestSerializeSingleton instance;
private TestSerializeSingleton() {}
public static TestSerializeSingleton getInstance(){
if(instance == null){
instance = new TestSerializeSingleton();
}
return instance;
}
public static void main(String[] args) {
String url = "DesignPatterns/src/main/resources/singleton.txt";
// 获取单例并序列化
TestSerializeSingleton singleton = TestSerializeSingleton.getInstance();
FileOutputStream fos = null;
ObjectOutputStream oos = null;
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
fos = new FileOutputStream(url);
oos = new ObjectOutputStream(fos);
oos.writeObject(singleton);
// 将实例反序列化出来
fis = new FileInputStream(url);
ois = new ObjectInputStream(fis);
Object o = ois.readObject();
log.info("他们是同一个实例吗?{}",o == singleton); // return false
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
try {
if(fos != null){
fos.close();
}
if(oos != null){
oos.close();
}
if(fis != null){
fis.close();
}
if(ois != null){
ois.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
输出结果如下:

readResolve()方法可以用于替换从流中读取的对象,在进行反序列化时,会尝试执行readResolve方法,并将返回值作为反序列化的结果,而不会克隆一个新的实例,保证jvm中仅仅有一个实例存在,所以在单例中添加readResolve方法:
java
public Object readResolve(){
return instance;
}
再次运行代码输出结果如下:
