单例模式
单例模式(Singleton)是一种常用的设计模式,它是创建型模式的一种,适用于一个类有且只有一个实例的情况,也就是说,单例模式确保了某个类只有一个实例(对象)存在。
单例模式定义的三个要素
① 定义私有的静态成员。
② 构造函数私有化。
③ 提供一个公有的静态方法以构造实例。
单例模式的实现方式
对于单例模式,一定要考虑并发状态下的同步问题,单例模式根据实例化对象时间的不同在实现代码时分为两种主流的实现方式,一种叫作饿汉式单例,另一种叫作懒汉式单例,这两种实现方式都是多线程安全的,但前者是天生多线程安全。
★ 饿汉式单例的实现方式:在单例类被加载时,就实例化一个对象。
★ 懒汉式单例的实现方式:调用取得实例的方法时才会实例化对象。
饿汉式单例模式的Java 实现代码如下:
// 单例模式(饿汉式)
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {};
public static Singleton getInstance() {
return instance;
}
}
懒汉式单例模式的Java 实现代码如下:
//单例模式(懒汉式)
public class Singleton {
private static Singleton instance;
private Singleton() {};
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在Java中,因为饿汉式实现方案天生线程安全,而懒汉式需要加号synchronized 关键字,影响的了性能,所以饿汉式单例性能要优子懒汉式单例。
单例模式的优点
① 避免了频繁地创建和销毁对象,减少了系统开销。
② 节省了内存空间,在内存中只有一个对象。
③ 提供了一个全局访问点。
单例模式的适用场景
✔ 针对某些需要频繁创建对象又频繁销毁对象的类。
✔ 需要经常用到对象,但创建时消耗大量资源。
✔ 针对确实只能创建一个对象的情况,比如某些核心交易类,只允许保持一个对象。
单例模式代码测试
编写懒汉式单例模式:
public class Singleton {
private static Singleton instance;
private Singleton() {};
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
编写main方法:
public class Test {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
if (s1 == s2) {
System.out.println("s1 和 s2是同一个实例");
}else {
System.out.println("s1 和 s2是不同的实例");
}
}
}
运行结果:
由此可以看出,单例模式只会创建一个实例对象。
单例模式对实例的对象做出来限制,为什么要限制呢?因为在一些情况下,当存在多个实例时,实例之间会相互影响,可能产生意想不到的Bug;但是,当我们可以确保只存在一个实例时,在这个前提下程序就可以放心的运行了。
常见问题
单例模式两种实现方式的区别?
单例模式主要有两种实现方式:饿汉式和懒汉式,两种实现方式是有区别的。
饿汉式是在加载类时就创建了类的一个对象;而懒汉式则是在调用实例方法getInstance()才会创建一个对象。
懒汉式单例模式的劣势?
从多线程的角度,懒汉式单例模式是不安全的,所以为了保障线程安全,一般的实现方式是在实例创建的方法getlnstance()前加 synchronized 保障线程安全,即: public static synchronized Singleton getlnstance(){} 。这种实现方案虽然简单,但是缺点也比较明显;这种实现方式降低了整个实例化的性能。
如何改进懒汉式单例模式?
不要在getlnstance()方法上进行同步,而是在方法内部进行同步。
具体操作如下:
① 进入方法后先检查实例是否已经存在,如果不存在则进入同步块;如果存在则直接返回该实例。
② 如果进入了同步块,则再次检查实例是否存在,如果不存在,就在同步块中创建实例;如果存在则直接返回该实例。
这种方法因为要经过两次“检查”,所以被称为“双重检查加锁机制”,这种方案既能实现线程安全,又能最大限度地减少性能影响。
双重检查加锁机制的实现代码:
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {};
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
需要注意的是,双重检查加锁机制用到一个关键字 volatile,用volatile 关键字修饰的变量不会被本地线程缓存,即变量的读写直接操作共享内存,这样可以保证多个线程正确使用该变量。
单例模式总结
单例模式有两种实现方式:饿汉式(类加载时创建实例) 和 懒汉式(调用实例方法时创建实例)。
单例模式只会创建一个实例,常用于某些核心类。
单例模式中的饿汉式是天生线程安全的,懒汉式需要后天进行改进才会线程安全。需要注意的是懒汉式如何进行性能改进,即“双重检查加锁机制”。