这是我参与 8 月更文挑战的第 5 天,活动详情查看: 8月更文挑战
懒汉式
当使用到该对象时,再创建对象。
优点:节约空间
缺点:性能延迟,非线程安全,需要手动实现线程安全
双重锁 Double check lock
1.0版本:
判断当前类变量是否为null,为null时代表还未创建,创建并返回
出现的问题:多线程并发的情况下,有A线程创建了,但是B线程这时候不知道A已经创建,又创建了1个,导致懒汉式失效
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if (lazyMan==null){
lazyMan = new LazyMan();
}
return lazyMan;
}
2.0版本:双重检测锁模式
sync将同步机制,解决未创建实例的情况下多线程并发可能会创建多个实例的问题。
但是为什么要双重检测呢?
- 如果单层检测,那么每个线程想要获得实例都需要走同步机制,十分耗时。在同步前面再加一层判断,当创建完之后,就无需走加锁步骤,节省时间
private static LazyMan lazyMan;
public static LazyMan getInstance2(){
if (lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
3.0模式:给实例加上volatile修饰
volatile可以防止指令重排
创建实例的过程中可能会指令重排:1.分配内存空间 2.初始化对象 3.将这个对象指向空间
重排后:1.分配内存空间 2.将空间指向某个对象 3.这个对象等于初始化的对象
那么这时候如果 1.2步时判断 空间以被指向,所以不为空。但是事实上这个对象还没有初始化,就会返回null。
private volatile static LazyMan lazyMan;
静态内部类
缺点:无法传参
实现方式:外部类加载时不会加载内部类,因此不会去初始化instance,不会占据空间。
当调用getInstance() 方法被调用时,才回去第一次初始化instance。
第一次调用getInstance()方法会导致虚拟机加载内部类。 确保线程安全,保证单例的唯一性,延迟单例的实例化。
/* 当创建外部类的实例时,不会创建内部类。
当调用getInstance方法时,才会生成单例。
* */
// 创建静态内部类
private static class SingleTonHolder{
// 实例化
private static SingleTon instance = new SingleTon();
}
/* 当调用该方法时,才回去加载静态内部类。静态内部类加载时,立刻会创建1个外部类的实例(相当于饿汉式)*/
private static SingleTon getInstance(){
return SingleTonHolder.instance;
}
public static void main(String[] args) {
// 调用静态方法创建单例
SingleTon instance = SingleTon.getInstance();
}
实现原理:Lazy initialization holder class模式
类级内部类:内部类被static修饰, 方法。 对象级内部类:内部类没有被static修饰 ,方法。
类级内部类可以直接通过外部类创建,不存在依赖关系。 对象级内部类需要外部类的实例才能创建,有依赖关系
类级内部类相当于外部类的成员,只有第一次被使用时才会加载。
JVM在某些情况下会自动加锁,就不需要自己手动sync:
1.静态初始化器(在静态字段上或 static{} 块中的初始化器)初始化数据。
2.访问final字段
3.创建线程之前创建对象
4.线程可以看见它将要处理的对象时
虚拟机再执行时会被加锁,同步。只会有一个线程可以执行方法,其他处于阻塞等待。执行方法后,其他线程唤醒就不会再进入clinit方法.
因此,在静态内部类的创建实例过程中时线程安全的。
枚举
可以防止反射破坏安全
public enum EnumSingle {
USER("11","yzy");
private String id;
private String name;
private EnumSingle(String id ,String name){
this.id = id;
this.name = name;
}
private String getId(){
return id;
}
public static void main(String[] args) {
// 获得单例对象
EnumSingle user = EnumSingle.USER;
}
}
饿汉式
加载类的时候,就直接创建对象。
优点:线程安全,第一次调用速度快。
缺点:无论是否用到,都要占据空间。