单例模式应该是我们接触的众多设计模式中的第一个,但是对于单例模式的一些细节地方对于初学者来说并不是很清楚,所以本文就来整理下单例模式。
单例模式的核心是保证一个类只有一个实例,并且提供一个访问实例的全局访问点。
实现方式 | 优缺点 |
---|---|
饿汉式 | 线程安全,调用效率高 ,但是不能延迟加载 |
懒汉式 | 线程安全,调用效率不高,能延迟加载 |
双重检测锁式 | 由于JVM底层内部模型原因,偶尔会出问题。不建议使用 |
静态内部类式 | 线程安全,资源利用率高,可以延时加载 |
枚举单例 | 线程安全,调用效率高,但是不能延迟加载 |
也就是类加载的时候立即实例化对象,实现的步骤是先私有化构造方法,对外提供唯一的静态入口方法,实现如下
/**
* 单例模式:饿汉式
* @author 波波烤鸭
*
*/
public class SingletonInstance1 {
// 声明此类型的变量,并实例化,当该类被加载的时候就完成了实例化并保存在了内存中
private static SingletonInstance1 instance = new SingletonInstance1();
// 私有化所有的构造方法,防止直接通过new关键字实例化
private SingletonInstance1(){}
// 对外提供一个获取实例的静态方法
public static SingletonInstance1 getInstance(){
return instance;
}
}
饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此,可以省略synchronized关键字
问题:如果只是加载本类,而不是要调用getInstance(),甚至永远没有调用,则会造成资源浪费!
/**
* 单例模式:懒汉式
* @author 波波烤鸭
*
*/
public class SingletonInstance2 {
// 声明此类型的变量,但没有实例化
private static SingletonInstance2 instance = null;
// 私有化所有的构造方法,防止直接通过new关键字实例化
private SingletonInstance2(){}
// 对外提供一个获取实例的静态方法,为了数据安全添加synchronized关键字
public static synchronized SingletonInstance2 getInstance(){
if(instance == null){
// 当instance不为空的时候才实例化
instance = new SingletonInstance2();
}
return instance;
}
}
此种方式在类加载后如果我们一直没有调用getInstance方法,那么就不会实例化对象。实现了延迟加载,但是因为在方法上添加了synchronized关键字,每次调用getInstance方法都会同步,所以对性能的影响比较大。
/**
* 单例模式:懒汉式
* 双重检测机制
* @author 波波烤鸭
*
*/
public class SingletonInstance3 {
// 声明此类型的变量,但没有实例化
private static SingletonInstance3 instance = null;
// 私有化所有的构造方法,防止直接通过new关键字实例化
private SingletonInstance3(){}
// 对外提供一个获取实例的静态方法,
public static SingletonInstance3 getInstance(){
if(instance == null){
SingletonInstance3 s3 = null;
synchronized(SingletonInstance3.class){
s3 = instance;
if(s3 == null){
synchronized(SingletonInstance3.class){
if(s3 == null){
s3 = new SingletonInstance3();
}
}
}
instance = s3;
}
}
return instance;
}
}
这个模式将同步内容下方到if内部,提高了执行的效率不必每次获取对象时都进行同步,只有第一次才同步创建了以后就没必要了。
问题:由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题。不建议使用。
/**
* 静态内部类实现方式
* @author 波波烤鸭
*
*/
public class SingletonInstance4 {
// 静态内部类
public static class SingletonClassInstance{
// 声明外部类型的静态常量
public static final SingletonInstance4 instance = new SingletonInstance4();
}
// 私有化构造方法
private SingletonInstance4(){}
// 对外提供的唯一获取实例的方法
public static SingletonInstance4 getInstance(){
return SingletonClassInstance.instance;
}
}
注意点:
/**
* 单例模式:枚举方式实现
* @author dengp
*
*/
public enum SingletonInstance5 {
// 定义一个枚举元素,则这个元素就代表了SingletonInstance5的实例
INSTANCE;
public void singletonOperation(){
// 功能处理
}
}
测试代码
public static void main(String[] args) {
SingletonInstance5 s1 = SingletonInstance5.INSTANCE;
SingletonInstance5 s2 = SingletonInstance5.INSTANCE;
System.out.println(s1 == s2); // 输出的是 true
}
优点:
缺点:
public static void main(String[] args) throws Exception, IllegalAccessException {
SingletonInstance1 s1 = SingletonInstance1.getInstance();
// 反射方式获取实例
Class c1 = SingletonInstance1.class;
Constructor constructor = c1.getDeclaredConstructor(null);
constructor.setAccessible(true);
SingletonInstance1 s2 = (SingletonInstance1)constructor.newInstance(null);
System.out.println(s1);
System.out.println(s2);
}
输出结果:
com.dpb.single.SingletonInstance1@15db9742
com.dpb.single.SingletonInstance1@6d06d69c
产生了两个对象,和单例的设计初衷违背了。
解决的方式是在无参构造方法中手动抛出异常控制
// 私有化所有的构造方法,防止直接通过new关键字实例化
private SingletonInstance2(){
if(instance != null){
// 只能有一个实例存在,如果再次调用该构造方法就抛出异常,防止反射方式实例化
throw new RuntimeException("单例模式只能创建一个对象");
}
}
public static void main(String[] args) throws Exception, IllegalAccessException {
SingletonInstance2 s1 = SingletonInstance2.getInstance();
// 将实例对象序列化到文件中
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("c:/tools/a.txt"));
oos.writeObject(s1);
oos.flush();
oos.close();
// 将实例从文件中反序列化出来
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("c:/tools/a.txt"));
SingletonInstance2 s2 = (SingletonInstance2) ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
}
输出结果:
com.dpb.single.SingletonInstance2@5c647e05
com.dpb.single.SingletonInstance2@4c873330
是两个不同的对象,同样破坏了单例模式,这种情况怎么解决呢
我们只需要在单例类中重写readResolve方法并在该方法中返回单例对象即可,如下:
package com.dpb.single;
import java.io.ObjectStreamException;
import java.io.Serializable;
/**
* 单例模式:懒汉式
* @author 波波烤鸭
*
*/
public class SingletonInstance2 implements Serializable{
// 声明此类型的变量,但没有实例化
private static SingletonInstance2 instance = null;
// 私有化所有的构造方法,防止直接通过new关键字实例化
private SingletonInstance2(){
if(instance != null){
// 只能有一个实例存在,如果再次调用该构造方法就抛出异常,防止反射方式实例化
throw new RuntimeException("单例模式只能创建一个对象");
}
}
// 对外提供一个获取实例的静态方法,为了数据安全添加synchronized关键字
public static synchronized SingletonInstance2 getInstance(){
if(instance == null){
// 当instance不为空的时候才实例化
instance = new SingletonInstance2();
}
return instance;
}
// 重写该方法,防止序列化和反序列化获取实例
private Object readResolve() throws ObjectStreamException{
return instance;
}
}
说明:readResolve方法是基于回调的,反序列化时,如果定义了readResolve()则直接返回此方法指定的对象,而不需要在创建新的对象!
大家只要关注相对值即可。在不同的环境下不同的程序测得值完全不一样
这几种设计模式如何选择
枚举式 好于 饿汉式
静态内部类式 好于 懒汉式