单例模式
所谓类的单例模式,就是采取一定的方法保证整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得该对象实例的方法(静态方法)。
比如hibernate的SessionFactory,它充当数据存储源的代理,并且创建Session对象,SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory就够。
八种方式:1.饿汉式(静态常量),2.饿汉式(静态代码块),3.懒汉式(线程不安全),4.懒汉式(线程安全,同步方法),5.懒汉式(线程安全,同步代码块),6.双重检查,7.静态内部类,8.枚举。
饿汉式(静态常量)
构造器私有化(防止外部new)
类的内部创建对象
向外暴露一个静态的公共方法getinstance
//饿汉式
public class Singleton {
// 1、构造器私有化
private Singleton() {
}
// 2、类的内部创建对象
private static final Singleton instance = new Singleton();
// 3、向外暴露一个静态的公共方法
public static Singleton getInstance() {
return instance;
}
}
优缺点:
优点:写法简单,类装载时完成实例化,避免线程同步问题。
缺点:实例化过早,没有lazy loading的效果,可能会造成内存浪费。
饿汉式(静态代码块)
构造器私有化
类的内部创建对象
在静态代码块中创建对象
向外暴露一个静态的公共方法
public class Singleton {
// 1、构造器私有化
private Singleton() {
}
// 2、类的内部声明对象
private static Singleton instance;
// 3、在静态代码块中创建对象
static {
instance = new Singleton();
}
// 4、向外暴露一个静态的公共方法
public static Singleton getInstance() {
return instance;
}
}
优缺点:类的实例化过程放在静态代码块中,优缺点和静态常量式的一样。
懒汉式(线程不安全)
构造器私有化
类的内部创建对象
向外暴露一个静态的公共方法,当使用该方法时才去创建instance
// 1、构造器私有化
private Singleton() {
}
// 2、类的内部声明对象
private static Singleton instance;
// 3、向外暴露一个静态的公共方法,当使用到该方法时,才去创建 instance
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
优缺点:相比前面的具有lazy loading的效果,但是只能在单线程时使用,多线程时,可能多个线程同时通过判断语句,会产生多个实例,这种方法不能使用,破坏了单例的初衷。
懒汉式(线程安全,同步方法)
构造器私有化
类的内部创建对象
向外暴露一个静态的公共方法,加入同步处理的代码,解决线程安全问题
public class Singleton {
// 1、构造器私有化
private Singleton() {
}
// 2、类的内部声明对象
private static Singleton instance;
// 3、向外暴露一个静态的公共方法,加入同步处理的代码,解决线程安全问题
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优缺点:解决了线程不安全的问题,但是方法都需要同步,而且实例化代码只需要运行一次,该方法效率太低了,不推荐使用。
懒汉式(线程安全,同步代码块)
构造器私有化
类的内部创建对象
向外暴露一个静态的公共方法,加入同步处理的代码
public class Singleton {
// 1、构造器私有化
private Singleton() {
}
// 2、类的内部声明对象
private static Singleton instance;
// 3、向外暴露一个静态的公共方法,加入同步处理的代码,解决线程安全问题
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
优缺点:改进了效率,但是在多线程中,假如一个线程进入了判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例,这种方法不能使用。
双重检查
构造器私有化
类的内部创建对象,同时用volatile关键字修饰修饰
向外暴露一个静态的公共方法,加入同步处理的代码块,并进行双重判断,解决线程安全问题
public class Singleton {
// 1、构造器私有化
private Singleton() {
}
// 2、类的内部声明对象,同时用`volatile`关键字修饰修饰
// volatile:保证变量在多线程之间的可见性
private static volatile Singleton instance;
// 3、向外暴露一个静态的公共方法,加入同步处理的代码块,并进行双重判断,解决线程安全问题
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
优缺点:Double Check概念保证了线程安全,延迟加载,效率高,实际开发中使用这种模式。
静态内部类
构造器私有化
定义一个静态内部类,内部定义当前类的静态属性
向外暴露一个静态的公共方法
public class Singleton {
// 1、构造器私有化
private Singleton() {
}
// 2、定义一个静态内部类,内部定义当前类的静态属性
private static class SingletonInstance {
private static final Singleton instance = new Singleton();
}
// 3、向外暴露一个静态的公共方法
public static Singleton getInstance() {
return SingletonInstance.instance;
}
}
优缺点:静态内部类方式在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用getlnstance方法,才会装载Singletonlnstance 类,从而完成 Singleton 的实例化,类的静态属性只会在第一次加载类的时候初始化,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的,避免了线程不安全,利用静态内部类特点实现延迟加载,效率高,推荐使用。
枚举
public enum Singleton {
INSTANCE;
public void sayHello() {
System.out.println("Hello World");
}
}
这借助 JDK1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
使用场景
单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多但又经常用到的对象(即:重量级对象)、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等。
推荐使用双重检查,静态内部类。