文章目录
1.定义
确保一个类只有一个实例,并提供一个全局访问点。
2.实现单例的两种方式
2.1.实现单例需要注意三点:
- 私有构造器,保证只能在本类内部实例对象。
- 提供私有静态变量保存实例对象。
- 提供公有静态方法返回实例对象。
2.2.饿汉式
定义: 饿汉式是在JVM加载类时候就实例化对象,后期每次调用返回的都是在加载类时候创建的对象。
缺点: 加重程序在创建时候的负担,而且如果后期没有调用则相对浪费资源。
代码如下:
public class SingletonTwo {
private static SingletonTwo instance = new SingletonTwo();
private SingletonTwo() {
}
public static SingletonTwo getInstance() {
return instance;
}
}
2.3.懒汉式
2.3.1.懒汉式–静态方法(非线程安全)
定义: 饿汉式是在公有静态方法getInstance()中实例化对象,即在调用时候才创建对象。示例代码如下:
public class SingletonOne {
private static SingletonOne instance;
private SingletonOne() {
}
public static SingletonOne getInstance() {
if (instance == null) {
instance = new SingletonOne();
}
return instance;
}
}
缺点: 懒汉式V1不能保证线程安全,多线程中仍会出现多个实例。如图:
如果上图代表两个线程,执行顺序按照A、B、C、D的顺序执行,还是会出现多个实例的情况。
解决多线程问题有四个办法:
- 在getInstance()上用synchronized关键字 。如图:
但是使用synchronized关键字会严重影响性能,如果对性能要求较高请慎重选择。 - 直接在静态变量instance处实例化对象,即饿汉式(上文2.2) 。
- 使用“双重检查加锁”,并且使用volatile关键字。
使用双重检查加锁即是在getInstance()内部先检查instance变量,然后使用synchronized关键字重新检查instance变量(,并且使用volatile关键字保证在instance实例化时,多个线程能正确的处理instance对象。
注:volatile关键字为java中使用的另一种同步机制,网上这边文章讲的不错:https://www.cnblogs.com/a31415926/p/6744485.html - 使用懒汉式V2解决。
2.3.2.懒汉式–静态方法+内部类(推荐)
定义: 使用内部类的私有静态变量创建实例化对象,然后通过静态方法调用内部类中实例化的对象。如下:
注:只有在第一次调用getInstance()时候,才会加载内部类并且创建SingletonOneV2的实例化对象
public class SingletonOneV2 {
private SingletonOneV2() {
}
static class SingletonInner {
private static SingletonOneV2 instance = new SingletonOneV2();
}
public static SingletonOneV2 getInstance() {
return SingletonInner.instance;
}
}
缺点: 通过反射能破坏懒汉式V2的单例结构。测试如下:
注:推荐使用此版本,开发人员注意不要使用反射获取单例类的实例就可避免此问题。
2.3.3.懒汉式–静态方法+内部类+synchronized
定义: 使用内部类的私有静态变量创建实例化对象,然后通过静态方法调用内部类中实例化的对象,并且使用synchronized解决懒汉式V2的反射的问题。如下:
public class SingletonOneV3 {
private static boolean inited = false;
private SingletonOneV3() {
synchronized (SingletonOneV3.class) {
if (inited == false) {
inited = !inited;
} else {
throw new RuntimeException("单例被破坏,不能创建实例");
}
}
}
static class SingletonInner {
private static SingletonOneV3 instance = new SingletonOneV3();
}
public static SingletonOneV3 getInstance() {
return SingletonInner.instance;
}
}
测试解决反射问题如下:
3.总结
单例模式创建通过饿汉式和懒汉式来时间单例对象的“优先”创建和“延迟”创建。其中延迟创建单例对象有分为通过静态方法、内部类、synchronized等创建,来避免反射及线程安全等出现的问题。还看到有在分布式程序中将单例对象序列化后通过文件存取扔保证单例特性的写法(具体参见:文件存取后如果保证单例的特性),不过鉴于没有接触这块及不知道使用场景等就没在此处列出。
参考资料:
《Head Firsh 设计模式》(中文版)