单例模式
介绍
类的单例设计模式,就是采取一定的方法来保证在整个软件系统中,某个类只存在一个对象实例。且该类只提供一个取得其对象实例的方法(静态方法)。
八种方式
单例模式有八种方式:
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(线程安全,同步代码块)
- 双重检查
- 静态内部类
- 枚举
步骤
步骤大致如下:
- 构造器私有化(防止外部new)
- 类的内部创建对象
- 向外提供一个静态公共方法(getInstance)
饿汉式(静态常量)
代码:
package singleton.type1;
public class Singleton {
private Singleton() {
}
private static final Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
测试代码:
package singleton.type1;
public class SingletonTest {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2);
System.out.println(instance.hashCode());
System.out.println(instance2.hashCode());
}
}
测试结果:
true
2018699554
2018699554
说明
- 优点:写法简单,类装载时就完成了实例化。基于class loader机制避免了线程同步问题
- 缺点:类装载时就完成了实例化,没有懒加载的效果。如果从始至终并未使用该实例,则造成内存浪费
结论:此模式可用,可能造成内存浪费
饿汉式(静态代码块)
代码:
package singleton.type2;
public class Singleton {
private Singleton() {
}
private static final Singleton instance;
static {
instance = new Singleton();
}
public static Singleton getInstance() {
return instance;
}
}
测试代码和测试结果同上
说明
- 这种方式和上面类似,只不过将类实例化的过程放在了静态代码块中。在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点同上。
结论:此模式可用,可能造成内存浪费
懒汉式(线程不安全)
代码:
package singleton.type3;
public class Singleton {
private Singleton() {
}
private static Singleton instance;
public static Singleton getInstance() {
if(null == instance) {
instance = new Singleton();
}
return instance;
}
}
说明
- 优点:起到了懒加载效果,只在需要的时候才实例化对象
- 缺点:只能在单线程下使用。如果在多线程下,一个线程进入了if(null == instance)的判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时就会产生多个实例。所有在多线程环境下不可使用该方式。
结论:实际开发中,不要用这种方式
懒汉式(线程安全,同步方法)
代码:
package singleton.type4;
public class Singleton {
private Singleton() {
}
private static Singleton instance;
public static synchronized Singleton getInstance() {
if(null == instance) {
instance = new Singleton();
}
return instance;
}
}
说明
- 优点:解决了线程安全问题
- 缺点:效率太低了。每个线程想获得类的实例对象时,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面想获取该类实例,直接返回就行了。方法同步效率太低
结论:实际开发中,不推荐使用这种方式
懒汉式(线程安全,同步代码块)
代码:
package singleton.type5;
public class Singleton {
private Singleton() {
}
private static Singleton instance;
public static Singleton getInstance() {
if(null == instance) {
synchronized(Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
说明
- 该方式本意是对第四张方式的改进,因为同步方法效率太低,改为同步产生实例的代码块
- 但是,这种方式并不能起到线程同步的作用。这里其实和第三种问题一样,加入一个线程进入了if(null == instance)语句块,还未执行下面的代码,另一个线程也通过了这个判断语句,还是会产生多个实例
结论:实际开发中, 不能使用这种方式
双重检查
代码:
package singleton.type6;
public class Singleton {
private Singleton() {
}
private static volatile Singleton instance;
public static Singleton getInstance() {
if(null == instance) {
synchronized(Singleton.class) {
if(null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
这里需要额外说明一下,volatile关键字使得某个线程对某个对象(变量)的修改,其他线程能立刻感知到。
说明
- Double-Check概念是多线程开发中经常出现的,如代码所示,我们进行了两次if(null == instance)检查,这样就可以保证线程安全了
- 第一次初始化时,即使是多线程,实例化代码也只执行一次。第一个线程进入同步块,实例化对象后,后面线程再次进入同步块时,判断if(null == instance),此时对象不为空,故第二次进入同步块实际什么也没做,之后返回实例化对象
- 之后再调用getInstance方法,在第一个if(null == instance)这里就跳过了同步块,直接返回实例化对象
- 综上,线程安全,延迟加载,效率较高
结论:实际开发中,推荐使用这种单例设计模式
静态内部类
代码:
package singleton.type7;
public class Singleton {
private Singleton() {
}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
说明
- 静态内部类在Singleton类加载时不会立即实例化,在需要时才会实例化,这里就是调用getInstance()方法时,才会装载SingletonInstance类,从而完成Singleton的实例化。实现了懒加载
- 类的静态属性只会在第一次类加载的时候初始化,所以这里JVM帮我们保证了线程安全,在类初始化时,别的线程无法进入
- 综上,线程安全,利用静态内部类特点实现延迟加载,效率高
结论:推荐使用
枚举
代码:
package singleton.type8;
public enum Singleton {
INSTANCE;
}
说明
- 借助JDK1.5添加的枚举来实现单例模式
- 不仅能避免多线程问题,还能防止反序列化重新创建新的对象
结论:推荐使用
JDK实例
在JDK中,java.lang.Runtime就是经典的单例模式(饿汉式)
总结
- 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
- 当要实例化一个单例类时,记得使用相应的方法获取对象,而不是使用new
- 单例模式使用场景:需要频繁进行创建和销毁对象、创建对象时耗时过多或耗费资源过多(即重量级对象),但又经常要用到的对象、工具类对象,频繁访问数据库或文件的对象(如数据源,session工厂等)
- 实际开发用类型:饿汉式(静态常量)、双重检查、静态内部类、枚举