要点归纳
- 单例模式
- 使用场景
- 实现方式
- 饿汉式
- 饱汉式
- 双重检查
- 静态内部类实现
- 枚举实现
- 登记式单例
单例模式
说起设计模式,耳熟能详恐怕就是单例模式。
这篇文章就整理下关于单例模式的实现方法,和使用场景。
单例模式:即保证该对象在jvm中,能够保证只有一个实例存在。
属于创建型模式,提供了一种创建对象的方式。
优点:
由于只有一个实例,减小了系统中内存的开销。
缺点:
扩展性能差,不能继承
不适用于需要变化的对象
关于 单例 or 多例 主要还是要看 适用的业务场景来选择
单例模式的实现规则:
只能有一个该类的实例对象
需要具有提供该实例的功能
使用场景
几种常见适用单例模式的场景例子:
- Spring中Bean对象默认创建方式
- 读取配置文件资源的类对象
- 数据库连接对象
- 日志管理对象
··· 等等
饿汉式
public class SingletonTest {
private SingletonTest() {}
public static final SingletonTest instance = new SingletonTest();
public SingletonTest getInstance() {
return instance;
}
}
优点:
1、线程安全
2、在类加载的同时已经创建好一个静态对象,调用时速度快
缺点:
不是一种懒加载模式,资源效率不高,如果getInstance()永远不会执行到,但类加载时就进行了初始化,浪费了内存。
饱汉式
class SingletonTest {
public static SingletonTest instance;
private SingletonTest() {}
public static SingletonTest getInstance() {
if (instance == null) {
instance = new SingletonTest();
}
return instance;
}
}
优点:
资源利用率高:避免了饿汉式的那种在没有用到的情况下创建事例,不执行getInstance()就不会被实例,可以执行该类的其他静态方法。
缺点:
线程不安全:多个线程同时访问的时候就可能同事创建多个实例,会存在拿到不同对象的情况。
双重检查
class SingletonTest {
private SingletonTest() {
}
public volatile static SingletonTest instance = null;
public static SingletonTest getInstance() {
if (instance == null) {
synchronized (Test.class) {
if (instance == null) {
instance = new SingletonTest();
}
}
}
return instance;
}
}
优点:
保证线程安全
资源利用率高:不执行getInstance()就不被实例,可以执行该类其他静态方法
instance 实例 是否需要用 volatile 修饰?
关于这个问题,需要知道
instance = new SingletonTest() 并不是原子形式完成的
其创建一个对象实例,分为三步:
1、分配对象内存
2、调用构造器方法,执行初始化
3、将对象引用赋值给变量。
问题原因:
JVM根据CPU的处理特性,在多核情况下会适当的进行指令重排序,从而最大程度上的提高性能。
·
比如 执行顺序 从原有的 [1- 2 - 3] 变为了 [1 - 3 - 2]
再此情况下:
1、线程1 执行 1 - 3 步骤,此时 instance 已被赋值了引用,此时不为空
2、线程2 进行 if (instance == null) 判断,不为空 则直接返回 instance对象
3、当调用 instance 时 就会 出现空指针异常
解决办法:
volatile 主要包含两个功能
1、保证可见性。
2、禁止指令重排序优化。(JDK1.5 +版本 )
静态内部类实现单例
public class SingletonTest {
private static class SingletonHolder {
private static final SingletonTest INSTANCE = new SingletonTest();
}
private SingletonTest (){}
public static final SingletonTest getInstance() {
return SingletonHolder.INSTANCE;
}
}
优点
保证线程安全:由于类的 加载机制 < clinit >()
延时加载(懒加载):加载外部类时,并不会加载内部类
枚举实现单例
public enum SingletonEnum {
INSTANCE;
private UserBean bean = null;
private SingletonEnum() {
bean = new UserBean();
}
public UserBean getInstance() {
return bean;
}
}
public static void main(String[] args) {
UserBean bean1 = SingletonEnum.INSTANCE.getInstance();
UserBean bean2 = SingletonEnum.INSTANCE.getInstance();
System.out.println(bean1 == bean2); // true
}
JDK 1.5 提供了 枚举:
按照上面的例子 其实现 如下:
public final class SingletonEnum extends Enum<SingletonEnum> {
public static final SingletonEnum INSTANCE;
public static SingletonEnum[] values();
public static SingletonEnum valueOf(String s);
static {};
}
优点
1、线程安全
2、防止反射
3、避免序列化问题
可以看到 INSTANCE 被声明为 static,并且枚举类型变量在JVM中是唯一的。
利用 反射枚举类创建实例 调用会抛出异常:
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
登记式单例
登记式实现单例,是利用Map作为登记簿维护实例,对于已经登记过的实例直接返回,对于没有进行登记的,则先登记然后再返回。
public class RegisterSingleton {
/**
* 使用map作为注册表,直接创建实例
*/
private static Map<String, RegisterSingleton> map = new HashMap<String, RegisterSingleton>();
/**
* 声明为protected构造函数,用于子类访问
*/
protected RegisterSingleton() {}
/**
* 判断是否已经存在namd对应实例,有则直接返回,没有新建登记再返回
*/
public static RegisterSingleton getInstance(String name) {
if (name == null) {
name = RegisterSingleton.class.getName();
}
if (map.get(name) == null) {
try {
//登记name对应的实例
map.put(name, (RegisterSingleton) Class.forName(name).newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//存在直接返回实例
return map.get(name);
}
public Map<String, RegisterSingleton> getMap() {
return map;
}
}
调用
public class Book extends RegisterSingleton {
static public Book getInstance() {
return (Book) getInstance(Book.class.getName());
}
}
public class User extends RegisterSingleton {
static public User getInstance() {
return (User) getInstance(User.class.getName());
}
}
该形式实现单例可继承;
Spring管理Bean就是利用登记式原理
为什么序列化 会 破坏单例模式?
当使用 ObjectInputputStream 的 readObject() 方式进行序列化时 会利用反射调用无参构造方法,从而创建一个新的对象。
以上内容,若有不足或错误,还望指正。欢迎探讨 ~
不止于前 未来可期 ···