确保一个类在任何情况下都绝对只有一个实例,并提供一个全 局访问点。单例模式是创建型模式。单例模式在现实生活中应用也非常广泛,例如,公司 CEO、部门经 理 等 。 J2EE 标 准 中 的 ServletContext 、 ServletContextConfig 等 、 Spring 框 架 应 用 中 的 ApplicationContext、数据库的连接池等也都是单例形式。
饿汉式单例
类一加载就初始化,它绝对线程安全,在线程还没出现以前就实例化了,不可能存在访问安全问题。
优点:没有加任何锁、执行效率比较高,用户体验比懒汉式单例模式更好。
缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能“占着茅坑不拉屎”。 尤其是如果一个系统有很多这样的类,一初始化就浪费很多内存。
Demo:
// 第一种写法
public class HungrySingleton {
private static final HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance() {
return instance;
}
}
// 第二种写法
public class HungrySingleton2 {
private static HungrySingleton2 instance = null;
static {
instance = new HungrySingleton2();
}
private HungrySingleton2() {}
public static HungrySingleton2 getInstance() {
return instance;
}
}
懒汉式单例模式
被外部类调用的时候内部类才会加载.
优点:没有线程安全问题
缺点:使用了synchronized,存在着性能问题
Demo
public class LazySingleton2 {
private static LazySingleton2 instance = null;
private LazySingleton2(){}
public synchronized static LazySingleton2 getInstance() {
if (instance == null) {
instance = new LazySingleton2();
}
return instance;
}
}
优化Demo2
使用双重检查锁
public class LazySingleton3 {
private volatile static LazySingleton3 instance = null;
private LazySingleton3(){}
public static LazySingleton3 getInstance() {
/**
* 这里为什么判断了不为空
* 如果去掉这层判断,就跟之前的synchronized方法差不多,
* 如果线程多了,还是阻塞很多
* 这里加了一层判断,就会少了一些阻塞
*/
if (instance != null) {
synchronized (LazySingleton3.class) {
if (instance != null) {
instance = new LazySingleton3();
}
}
}
return instance;
}
}
这里会在后面的文章讲下为什么会有volatile这个?
优化Demo3 使用静态内部类.
顾了内存浪费和synchronized性能问题
- 为什么兼顾了内存浪费?
- 因为静态内部类是要外部类调用才初始化
- 性能问题:因为是已经初始化好了
public class LazySingleton4 {
private LazySingleton4(){}
public static final LazySingleton4 getInstance() {
return inner.instance;
}
private static class inner {
private static final LazySingleton4 instance = new LazySingleton4();
}
}
反射破坏单例
// 单例类
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton(){
}
public static final Singleton getInstance() {
return instance;
}
}
// 测试类
public class ReflectDestroySingleton {
public static void main(String[] args) {
Class<Singleton> singletonClass = Singleton.class;
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = null;
try {
Constructor<Singleton> constructor = singletonClass.getDeclaredConstructor();
constructor.setAccessible(true);
instance2 = constructor.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
System.out.println(instance1 == instance2);
}
}
上面运行结果为false,就违背了单例,如果饿汉式单例我们可以在构造方法里面做下文章.
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton(){
// 防止反射破坏
if (instance != null) {
throw new RuntimeException("不能new了");
}
}
public static final Singleton getInstance() {
return instance;
}
}
这样反射创建就会报错了。
序列化破坏单例
Demo
// 单例类 Serializable是序列化必须实现的接口
public class Singleton implements Serializable {
private static final Singleton instance = new Singleton();
private Singleton(){}
public static final Singleton getInstance() {
return instance;
}
}
// 序列化破坏
public class SerializableDestroySingleton {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = null;
try {
FileOutputStream fileOutputStream = new FileOutputStream("instance.obj"); // 放在instance.obj,随便写
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(instance1);
// 反序列化
FileInputStream fileInputStream = new FileInputStream("instance.obj");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
instance2 = (Singleton) objectInputStream.readObject();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(instance1 == instance2);
}
}
上面运行结果为false。 违背了单例,怎么解决呢?
public class Singleton implements Serializable {
private static final Singleton instance = new Singleton();
private Singleton(){}
public static final Singleton getInstance() {
return instance;
}
// 加上这个方法,序列化就不能破坏了 必须要这样写
private Object readResolve() {
return instance;
}
}
为什么呢?加了这个方法,运行结果就为true了。 从源码去进行剖析。
我们点进readObject()方法
点进readOrdinaryObject方法,
继续往后看。
就到这里了。 继续。
然后将那个方法返回的值,赋值给rep。
所以刚才运行结果为true,但是在内部还是实例化了一次,只不过覆盖了。
注册式单例
第一种使用枚举。
优点:
- 反射不能破坏
- 序列化不能被破坏
- 线程安全
不能破坏的原因,继续往下看,下面要分析。
Demo
public enum RegisteredSingleton {
INSTANCE;
// 这里只是为了这个实例可以存些数据,去掉也可以
private Object data;
public void setData(Object data) {
this.data = data;
}
public Object getData() {
return data;
}
}
尝试反射破坏枚举单例
public static void main(String[] args) {
// 验证反射不能破坏枚举单例
Class<RegisteredSingleton> singletonClass = RegisteredSingleton.class;
/**
* 首先看源码Enum这个类没有无参的构造,有一个有参构造 protected Enum(String name, int ordinal)
*/
try {
Constructor<RegisteredSingleton> constructor = singletonClass.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
RegisteredSingleton singleton = constructor.newInstance();
System.out.println(singleton);
/**
* 运行结果报错 : Cannot reflectively create enum objects
* at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
这行报错,点进去看
* if ((clazz.getModifiers() & Modifier.ENUM) != 0)
* throw new IllegalArgumentException("Cannot reflectively create enum objects");
* 上面这句代码已经很清楚了,有枚举修饰符的报错
*/
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
尝试序列化破坏枚举单例
public static void main(String[] args) {
// 验证序列化破坏枚举单例
RegisteredSingleton instance = RegisteredSingleton.INSTANCE;
try {
FileOutputStream fileOutputStream = new FileOutputStream("instance.obj");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(instance);
FileInputStream fileInputStream = new FileInputStream("instance.obj");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
RegisteredSingleton instance2 = (RegisteredSingleton) objectInputStream.readObject();
System.out.println(instance == instance2);
/**
* 运行结果为true,那么为什么没被破坏呢?看下源码readObject方法
* Enum<?> en = Enum.valueOf((Class)cl, name);
* 底层调用的是这个
* 再看看这个valueOf方法,底层:T result = enumType.enumConstantDirectory().get(name);
* 他是从这个里面获取的
*private volatile transient Map<String, T> enumConstantDirectory = null;
*/
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
第二种是容器式单例。
public class ContainerModeSingleton {
private ContainerModeSingleton(){}
private static Map<String,Object> map = new ConcurrentHashMap<>();
public static Object getInstance(String className) {
// 1.第一种写法
synchronized (map) {
if (!map.containsKey(className)) {
Object obj = null;
try {
obj = Class.forName(className).newInstance();
map.put(className,obj);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return obj;
}
return map.get(className);
}
}
}