一、是什么
确保一个类只有一个实例,并提供一个全局访问点
一般分类两大类: 饿汉模式、懒汉模式
使用: 以前在线白鹭H5游戏时,因为有很多的场景类, 而每个场景类不需要创建很多遍, 所以使用单例模式
二、示例
1. 饿汉模式
/**
* 饿汉模式, 线程安全, 但默认就创建实例, 占用空间
*/
public class Singleton1 {
private static final Singleton1 instance = new Singleton1();
private Singleton1() {
}
public static Singleton1 getIntance() {
return instance;
}
}
用空间换时间,默认就创建实例,所以没有线程安全问题
2. 懒汉模式
/**
* 懒汉模式, 线程不安全
*/
public class Singleton2 {
private static Singleton2 instance = null;
private Singleton2() {
}
public static Singleton2 getInstance() {
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
}
现在不安全在于,多个线程访问getInstance()时,当一个线程已经初始化了,而另外一个线程并没有感知,又重新创建了实例,这时候就不是单例
2.1 双检锁 Double-check
/**
* 懒汉模式--双检索
*/
public class SingletonDoubleCheck {
private static SingletonDoubleCheck instance = null;
private SingletonDoubleCheck() {
}
public static SingletonDoubleCheck getInstance() {
if (instance == null) {
synchronized (SingletonDoubleCheck.class) {
if (instance == null) {
instance = new SingletonDoubleCheck();
}
}
}
return instance;
}
}
为了在懒汉模式的基础上,保证线程安全, 出现了双检锁的设计,但是有出现了另一个问题。
在new SingletonDoubleCheck()时,是非原子性的,实际分为三步
- new 分配内存空间
- 初始化对象
- 将对象指向刚分配的内存空间
但JVM编译器,为了性能考虑,可能重新排序2,3两个, 变为:
- new 分配内存空间
- 将对象指向刚分配的内存空间
- 初始化对象
举例说明:
线程1检查到instance为空,获取锁,再次检查instance为空,为instance分配内存空间,指向内存空间,这时线程2检查到instance不为空,直接返回instance,但此时对象还没有初始化完成
2.2 双检锁 线程安全
/**
* 使用volatile关键字的双检锁
*/
public class SingletonDoubleCheck2 {
/**
* volatile关键字保证我在锁instance时, 禁止JVM重排序
*/
private volatile static SingletonDoubleCheck2 instance = null;
private SingletonDoubleCheck2() {
}
public static SingletonDoubleCheck2 getInstance() {
if (instance == null) {
// 再次减少锁的范围, 只锁instance变量
synchronized (instance) {
if (instance == null) {
instance = new SingletonDoubleCheck2();
}
}
}
return instance;
}
}
使用volatile关键字来禁止JVM重排序
3.3 内部类实现
/**
* 静态内部类实现 -- 延迟加载
*
* 天生线程安全
*/
public class Singleton3 {
/**
* 私有化构造
*/
private Singleton3() {
System.out.println("初始化");
}
/**
* 静态内部类
*/
private static class InnerObject{
private static Singleton3 instance = new Singleton3();
}
public static Singleton3 getInstance() {
return InnerObject.instance;
}
public static void main(String[] args) {
getInstance();
}
}
3.4 静态代码实现
/**
* 懒汉模式 -- 静态代码块实现
*/
public class Singleton4 {
private static Singleton4 instance = null;
static {
instance = new Singleton4();
}
public static Singleton4 getInstance() {
return instance;
}
}
外部类加载时并不需要立即加载内部类,所以可以起到延时加载的目录,
三、总结
单例模式是一个创建型的设计模式,能够帮助开发者创建一个唯一的实例
使用的还是挺频繁的