目录
单例模式
所谓的单例设计模式,就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory就够,这时就会使用单例模式。
单例模式有八种方式
1、饿汉式(静态常量)
静态常量在类加载阶段的准备阶段完成分配空间,赋值在初始化阶段完成
补充JVM类加载知识:
- static 变量分配空间和赋值是两个部分,分配空间在准备阶段完成,赋值在初始化阶段完成
- 如果 static 变量是final 的基本类型或字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成
- 如果 static 变量是final 的,但属于引用类型,那么赋值也会在初始化阶段完成
/**
* 单例模式(饿汉式-静态变量)
* 1、构造器私有化
* 2、类内部创建对象实例
* 3、对外暴露一个静态的公共方法
*/
public class Singleton1 {
// 1、构造器私有化,外部不能new
private Singleton1() {}
// 2、类内部创建对象实例
private final static Singleton1 instance = new Singleton1();
// 3、对外暴露一个静态的公共方法,返回实例对象
public static Singleton1 getInstance() {
return instance;
}
}
2、饿汉式(静态代码块)
静态常量和静态代码块原理相同,实现形式不同
/**
* 单例模式(饿汉式-静态代码块)
*/
public class Singleton2 {
// 1、构造器私有化,外部不能new
private Singleton2() {}
// 2、类内部创建对象实例
private static Singleton2 instance;
static {
instance = new Singleton2();
}
// 3、对外暴露一个静态的公共方法,返回实例对象
public static Singleton2 getInstance() {
return instance;
}
}
3、懒汉式(线程不安全)
此方法线程不安全,在多线程环境下,进入到if (instance == null)
语句内的线程可能有多个,此时Singleton1
类会被实例化多次
/**
* 单例模式(懒汉式-线程不安全)
*/
public class Singleton1 {
private static Singleton1 instance;
private Singleton1() {}
// 提供一个静态公有方法,当使用到该方法时,才去创建 instance
public static Singleton1 getInstance() {
if (instance == null) {
instance = new Singleton1();
}
return instance;
}
}
4、懒汉式(线程安全,同步方法)效率低
为static getInstance()
方法加synchronized
关键字,锁加在类上,每个访问该方法的线程竞争同一把锁,同一时刻只有一个线程能进入这个方法中,并完成对象创建,方法执行完后释放锁,下一个获取锁的线程进入方法后instance
已经被创建,直接返回instance
但方法级别的synchronized
锁的粒度大,效率很低,不推荐使用
/**
* 单例模式(懒汉式-线程安全,同步方法)效率低
*/
class Singleton2 {
private static Singleton2 instance;
private Singleton2() {}
// 提供一个静态公有方法,当使用到该方法时,才去创建 instance
public synchronized static Singleton2 getInstance() {
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
}
5、懒汉式(线程不安全,同步代码块)错误!不能使用
和懒汉式(线程安全,同步方法)相似,但使用synchronized
关键字加在if
判断语句内部,实际上它是线程不安全的
有人会说将synchronized
关键字加在if
语句外部,这和加到方法上基本是一样的,效率依旧很低
/**
* 单例模式(懒汉式-同步代码块)错误!不能使用
*/
class Singleton3 {
private static Singleton3 instance;
private Singleton3() {}
// 提供一个静态公有方法,当使用到该方法时,才去创建 instance
public static Singleton3 getInstance() {
if (instance == null) {
synchronized (Singleton3.class) {
instance = new Singleton3();
}
}
return instance;
}
}
6、双重检查 *推荐使用
volatile是Java虚拟机提供的轻量级的同步机制,它有3个特性:
- 1、保证可见性:变量改变值后能即时让其他阻塞在synchronized的线程发现,从而跳过内部的if循环
- 2、不保证原子性:为整个对象创建的过程不被其他线程打断,需要加入synchronized锁
- 3、禁止指令重排:
对象的创建分为三部(instance = new Singleton1();)
//1:分配对象的内存空间
memory = allocate();
//2:初始化对象
ctorInstance(memory);
//3:设置instance指向刚分配的内存地址
instance = memory;
即编译器或处理器为提高性能改变代码执行顺序,如果执行顺序为132,对象分配了地址,但并未初始化,其他线程拿到的instance
就是有问题的,volatile关键字禁止指令重排可解决这一问题
/**
* 单例模式(双重检查)
*
* volatile是Java虚拟机提供的轻量级的同步机制,它有3个特性:
* 1、保证可见性
* 2、不保证原子性
* 3、禁止指令重排
*/
public class Singleton1 {
private static volatile Singleton1 instance;
private Singleton1() {}
// 提供一个静态公有方法,加入双重检查代码,解决线程安全问题,同时解决懒加载问题
public static Singleton1 getInstance() {
if (instance == null) {
synchronized (Singleton1.class) {
if (instance == null) {
instance = new Singleton1();
}
}
}
return instance;
}
}
7、静态内部类 *推荐使用
静态内部类在外部类被装载时不会被立即装载,而是在被第一次调用时才会装载,类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类的初始化时,别的线程是无法进入的。
/**
* 单例模式(静态内部类)
*
* 优点:避免了线程不安全,利用静态内部类的特点实现延迟加载,效率高
* 结论:推荐使用
*/
public class Singleton1 {
private Singleton1() {}
private static class SingletonInstance {
private static final Singleton1 INSTANCE = new Singleton1();
}
public static Singleton1 getInstance() {
return SingletonInstance.INSTANCE;
}
}
8、枚举 *推荐使用
借助 JDK1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重写创建新的对象。
/**
* 结论:推荐使用
*
* 枚举的本质是一个类,继承了 Enum,并修饰 final表示不能被别的类继承
* 枚举的成员变量实际上是自身的一个实例,并且是static final修饰的
* 在静态代码块种被实例化,在类加载阶段执行,所以是饿汉式
*
* public final class Singleton1 extends Enum {
* public static final Singleton1 INSTANCE;
* static {
* INSTANCE = new Singleton1("APPLE", 0, 1);
* $VALUES = (new Singleton1[] {INSTANCE});
* }
* }
*
*/
public enum Singleton1 {
INSTANCE;
// 枚举本质也是类,也可以定义成员方法
public void sayOk() {
System.out.println("ok");
}
}