推荐先阅读
:Java设计模式
概述
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式的角色:
单例模式分两种:
- 饿汉式:类加载就会导致该单实例对象被创建
- 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
饿汉式-实现方式1:静态变量
创建该类对象
该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。
instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。
public class Singleton { private Singleton() {}
private static Singleton instance = new Singleton();
public static Singleton getInstance() { return instance; } }
|
饿汉式-实现方式2:在静态代码块
中创建该类对象
该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。
所以和饿汉式的方式1基本上一样,当然该方式也存在内存浪费问题。
public class Singleton { private Singleton() {}
private static Singleton instance;
static { instance = new Singleton(); }
public static Singleton getInstance() { return instance; } }
|
饿汉式-实现方式3:枚举方式
极力推荐的单例实现模式,只会装载一次,线程安全。唯一一种不会被破坏的单例实现模式。
public enum Singleton { INSTANCE; }
|
懒汉式-实现方式1:双重检查锁
public class Singleton { private Singleton() {}
private static volatile Singleton instance;
public static Singleton getInstance() { if(instance != null) { return instance; }
synchronized (Singleton.class) { if(instance != null) { return instance; } instance = new Singleton(); return instance; } } }
|
- 当调用getInstance()时才创建对象。这样就实现了懒加载的效果。
- 在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。要解决双重检查锁模式带来空指针异常的问题,只需要使用
volatile
关键字, volatile
关键字可以保证可见性和有序性。
- 添加
volatile
的双重检查锁模式是一种比较好的单例实现模式,解决了单例、性能、多线程安全问题。
懒汉式-实现方式2:静态内部类方式
- 静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中, 是不会加载静态内部类的,
- 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。
- 静态属性由于被
static
修饰,保证只被实例化一次,并且严格保证实例化顺序。
public class Singleton { private Singleton() {}
private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); }
public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
|
静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
存在的问题
使上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外
。有两种方式,分别是序列化
和反射
。
1.序列化反序列化
public class Singleton implements Serializable { private Singleton() {}
private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); }
public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
public class Test { public static void main(String[] args) throws Exception { writeObject2File(); Singleton s1 = readObjectFromFile(); Singleton s2 = readObjectFromFile();
System.out.println(s1 == s2); }
private static Singleton readObjectFromFile() throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\a.txt")); Singleton instance = (Singleton) ois.readObject();
return instance; }
public static void writeObject2File() throws Exception { Singleton instance = Singleton.getInstance(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\a.txt")); oos.writeObject(instance); } }
|
上面代码运行结果是false
,表明序列化和反序列化已经破坏了单例。
2.反射
public class Singleton { private Singleton() {} private static volatile Singleton instance;
public static Singleton getInstance() { if(instance != null) { return instance; }
synchronized (Singleton.class) { if(instance != null) { return instance; } instance = new Singleton(); return instance; } } }
public class Test { public static void main(String[] args) throws Exception { Class clazz = Singleton.class; Constructor constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true);
Singleton s1 = (Singleton) constructor.newInstance(); Singleton s2 = (Singleton) constructor.newInstance();
System.out.println(s1 == s2); } }
|
上面代码运行结果是false
,表明序列化和反序列化已经破坏了单例。
注意
:枚举方式不会出现这两个问题。
问题的解决
1.序列化、反序列方式破坏单例模式的解决方法
在Singleton类中添加readResolve()
方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。
public class Singleton implements Serializable { private Singleton() {}
private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); }
public static Singleton getInstance() { return SingletonHolder.INSTANCE; } private Object readResolve() { return SingletonHolder.INSTANCE; } }
|
源码解析:
ObjectInputStream类
public final Object readObject() throws IOException, ClassNotFoundException{ int outerHandle = passHandle; try { Object obj = readObject0(false); } } private Object readObject0(boolean unshared) throws IOException { ... try { switch (tc) { ... case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared)); ... } } finally { depth--; bin.setBlockDataMode(oldMode); } } private Object readOrdinaryObject(boolean unshared) throws IOException { ... obj = desc.isInstantiable() ? desc.newInstance() : null; ... if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); ... } return obj; }
|
2.反射方式破解单例的解决方法
public class Singleton { private Singleton() { if(instance != null) { throw new RuntimeException(); } }
private static volatile Singleton instance;
public static Singleton getInstance() { if(instance != null) { return instance; }
synchronized (Singleton.class) { if(instance != null) { return instance; } instance = new Singleton(); return instance; } } }
|
这种方式比较好理解。当通过反射方式调用构造方法进行创建创建时,直接抛异常。不运行此中操作。
JDK源码解析-Runtime类
Runtime类就是使用的单例设计模式。
public class Runtime { private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() { return currentRuntime; }
private Runtime() {} ... }
|
使用Runtime类中的方法
public class RuntimeDemo { public static void main(String[] args) throws IOException { Runtime runtime = Runtime.getRuntime();
System.out.println(runtime.totalMemory()); System.out.println(runtime.maxMemory());
Process process = runtime.exec("ipconfig"); InputStream inputStream = process.getInputStream(); byte[] arr = new byte[1024 * 1024* 100]; int b = inputStream.read(arr); System.out.println(new String(arr,0,b,"gbk")); } }
|