更多知识,请移步我的小破站:http://hellofriend.top
什么是单例设计模式?
单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。
通过单例模式可以保证系统中,应用该模式的类只有一个实例。即一个类只有一个对象实例。
在Java语言中,单例带来了两大好处:
- 对于频繁使用的对象(数据源、Session工厂),可以省略创建对象所花费的时间,这对于重量级的对象而言,是非常可观的一笔系统开销。
- 由于
new
操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。
具体实现
需要:
(1)将构造方法私有化,使其不能在类的外部通过new
关键字实例化该类对象。
(2)在该类内部产生一个唯一的实例化对象,并且将其封装为 private static
类型。
(3)定义一个静态方法返回这个唯一对象。
实现一:饿汉式 / 静态常量
- 立即加载就是使用类的时候已经将对象创建完毕(不管以后会不会使用到该实例化对象,先创建了)。常见的实现办法就是直接 new 实例化。
代码如下:
public class HungrySingletonStaticInstance {
public static void main(String[] args) {
System.out.println("======HungrySingletonStaticInstance======");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Singleton {
// 将自身实例化对象设置为一个属性,并用static、final修饰
private final static Singleton instance = new Singleton();
// 构造方法私有化
private Singleton() {}
// 提供静态方法返回该实例
public static Singleton getInstance() {
return instance;
}
}
“饿汉模式” 的优缺点分析:
- 优点:实现起来简单,没有多线程同步问题。
- 缺点:当类
Singleton
被加载的时候,会初始化static
的instance
,静态变量被创建并分配内存空间,从这以后,这个static
的instance
对象便一直占着这段内存,可能造成内存浪费(即便你还没有用到这个实例)。当类被卸载时,静态变量被摧毁,并释放所占有内存,在某些特定条件下会耗费内存。 - 补充:如果方法内有其他
static
方法,调用该方法此类也会加载初始化。
实现二:饿汉式 / 静态代码块
public class HungrySingletonStaticBlock {
public static void main(String[] args) {
System.out.println("======HungrySingletonStaticBlock======");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Singleton {
private static Singleton instance;
static {
instance = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
- 如果方法内有其他
static
方法,调用该方法此类也会加载初始化。
实现三:懒汉式 / 线程不安全
- 延迟加载就是调用
getInstance()
方法时实例才被创建(先不急着实例化出对象,等要用的时候才创建出来)。常见的实现方法就是在getInstance()
方法中进行 new 实例化。
public class LazyLoadingSingletonThreadUnSafe {
public static void main(String[] args) {
System.out.println("======LazySingletonThreadUnsafe======");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Singleton {
// 将自身实例化对象设置为一个属性,并用static修饰
private static Singleton instance;
// 构造方法私有化
private Singleton() {}
// 静态方法返回该实例
public static Singleton getInstance() {
if (instance == null) {
//TODO 线程在这里被阻塞,则此时对象没有被创建,UnSafe
instance = new Singleton();
}
return instance;
}
}
“懒汉模式” 的优缺点分析:
- 优点:实现起来比较简单,当类
Singleton
被加载的时候,静态变量static
的instance
未被创建并分配内存空间,当getInstance()
方法第一次被调用时,初始化instance
变量,并分配内存,因此在某些特定条件下会节约了内存。 - 缺点:在多线程环境中,这种实完现方法是错误的,不能保证单例的状态。
- 补充:如果方法内有其他
static
方法,调用该方法此类不会加载初始化。
实现四:懒汉式 / 线程安全 Sync
- 静态方法返回该实例,加
Synchronized
关键字实现同步。
public class LazyLoadingSingletonSync {
public static void main(String[] args) {
System.out.println("======LazyLoadingSingletonSync======");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Singleton {
// 将自身实例化对象设置为一个属性,并用static修饰
private static Singleton instance;
// 构造方法私有化
private Singleton() {}
// 静态方法返回该实例
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
线程安全的“懒汉模式”(加锁)的优缺点:
- 优点:在多线程情形下,保证了“懒汉模式”的线程安全。
- 缺点:在多线程情形下,
synchronized
方法通常效率低,显然这不是最佳的实现方案。 - 补充:如果方法内有其他
static
方法,调用该方法此类不会加载初始化。
实现五:DCL双重检查锁定机制
(DCL:Double Checked Locking)
public class LazyLoadingSingletonDCL {
public static void main(String[] args) {
System.out.println("======LazyLoadingSingletonDCL======");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Singleton {
// 将自身实例化对象设置为一个属性,并用 volatile、static 修饰
private volatile static Singleton instance;
// 构造方法私有化
private Singleton() {}
// 静态方法返回该实例
public static Singleton getInstance() {
// 第一次检查instance是否被实例化出来
if (instance == null) {
synchronized (Singleton.class) {
// 某个线程取得了类锁,实例化对象前第二次检查 instance 是否已经被实例化
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
- 该方法是单例模式推荐的实现方式。内存占用率高,效率高,线程安全,多线程操作原子性。
- 如果方法内有其他
static
方法,调用该方法此类不会加载初始化。
实现六:静态内部类
public class StaticInternalSingleton {
public static void main(String[] args) {
System.out.println("======StaticInternalSingleton======");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
//加载 Singleton 类时并不会加载内部类
private static final Singleton INSTANCE = new Singleton();
}
}
- 第一次加载
Singleton
类时并不会加载内部类初始化Instance
,只有第一次调用getInstance
方法时虚拟机加载SingletonHolder
并初始化Instance
,这样不仅能确保线程安全也能保证Singleton
类的唯一性,所以推荐使用静态内部类单例模式。
实现七:枚举单例
该方法借助 JDK 1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
public class EnumSingleton {
public static void main(String[] args) {
System.out.println("======EnumSingleton======");
Singleton instance1 = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println("instance1 == instance2:" + (instance1 == instance2));//true
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
instance1.method();
instance2.method();
}
}
enum Singleton {
INSTANCE;
public void method() {
System.out.println("EnumSingleton");
}
}
JDK 中单例模式的应用之 Runtime 类
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
//TODO 私有化构造器
private Runtime() {}
很显然,JDK 中的 Runtime 类使用的是本文的 实现一:饿汉式 / 静态常量来实现的,由于 Runtime 类在程序运行中一定会使用到,所以并不会带来饿汉式的空间浪费问题。
小总结
(1)单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统的性能。
(2)当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new。