简单介绍
单例模式是最简单的设计模式之一,提供了一种创建对象的方式,确保在整个系统中只有一个对象被创建.单例模式解决了频繁创建重复对象的问题节约资源,可以省略创建对象所需要花费的时间,对于一些重量级对象而言这点是很重要的.并且因为不需要频繁创建对象 GC 的压力也会有所减轻.
单例模式的一些实现方式
通常来说在 Java 中的单例模式分为饿汉式和懒汉式,而且单例类需要一个 private 的构造函数防止被其他代码实例化.下面来具体说一下java 中单例模式的实现.
饿汉式
public class Singleton{
private static Singleton instance =new Singleton();
//私有化构造方法
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
饿汉式的单例模式代码简单,线程安全.先创建对象,然后等待调用首先私有化构造方法,防止别人使用new 创建对象.通过classLoader机制保证了单例对象的唯一性 但是不能确保instance 是在调用getInstance()方法的时候生成的不能达到懒加载效果
懒汉式
public class Singleton{
private static Singleton instance;
private Singleton(){}
//加入 synchronize 保证线程安全
public synchronized static Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
为了达到懒加载的效果,我们使用懒汉式的单例模式,在第一次调用方法getInstance()的时候才去创建对象.可以达到延迟加载的效果并且加入了 synchronize 保证线程安全,但每次调用代码的时候都要加锁,性能比较低还有可能发生阻塞
DCL双重校验锁
public class Singleton{
//volatile防止指令重排序
private static volatile Singleton instance=null;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
//加入第二次校验
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}
双重校验锁就是为了解决上述问题而存在的,先检查实例是否存在然后再去创建,可以不用每次调用方法都获取同步锁性能会有一些提升,减小的锁的颗粒度.但是 java 对象的创建和赋值不是一步操作的,有可能先去赋值给中instance之后才去创建 Singleton 这时添加volatile关键字防止指令重排序解决了这个问题.
对象创建的过程:
在代码的第 12 行 instance =new Singleton();大致分为三个过程 1.分配对象的内存空间 此时 instance !=null
2.初始化对象
3.将instance 指向分配的内存空间
其中2 和 3不一定是有序的 所以线程 B 会访问到一个还未初始化的对象
静态内部类
public class Singleton{
private Singleton(){}
public static class SingleHoler{
public static final Singleton instance=new Singleton();
}
public static Singleton getInstance(){
return SingleHoler.instance;
}
}
看过繁琐的DCL后 下面介绍一种简洁的单例模式静态内部类.当Singleton被创建的时候不会去加载SingleHoler,只有第一次调用getInstance()方法时才回去创建instance,加载SingleHoler将常量池中的符号引用替换成直接引用,这种方式不仅保证了线程安全而且可以达到延迟加载的效果.
classload机制
解决重排序的方法有两种,第一种就是使用 volatile ,第二种则是现在要介绍的方法
调用类的静态成员(非字符串常量)的时候会导致类(SingleHoler)的初始化.并且在执行类的初始化期间,JVM 会获取一个初始化锁,这个锁可以同步多个线程对同一个类的初始化.
类加载的步骤:
将符号引用替换成直接引用是在解析的阶段完成的.
最佳实践
public enum Singleton{
INSTANCE;
public void print(){
System.out.println("快乐就完事了!");
}
}
这种方法在功能上与公有域方法相近,但是它更加简洁,无偿提供了序列化机制,绝对防止多次实例化,即使是在面对复杂序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。 --《Effective Java 中文版 第二版》
简单到不能再简单了啊.jvm 在加载枚举类的时候会使用loadClass方法使用同步代码块解决线程安全问题.使用 enum 的单例模式还能避免反序列化破坏单例并且不能被反射攻击.