创建单例类的步骤:
1、私有化该类的构造函数。
2、通过new在本类中创建一个本类对象。
3、提供一个公有的静态方法,将创建的对象返回。
单例类因为不允许其他程序用new来创建该类对象,所以只能将单例类中的方法定义成静态的(随类的加载而加载),静态方法不能访问非静态的成员,故只能> > 将该类中new的本类对象变成静态的。
https://www.cnblogs.com/william-dai/p/10938666.html
单例模式懒汉式和饿汉式区别?
- 饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,懒汉式本身是非线程安全的,为了实现线程安全可以加synchronized锁。
- 资源加载和性能:饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成。而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
原文链接:https://blog.csdn.net/qq_35098526/article/details/79893628
1. 懒汉式:用到时再去创建
//非线程安全的单例模式
public class Singleton {
//懒汉式单例,只有在调用getInstance时才会实例化一个单例对象
public static Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if(user==null){ //step 1.
singleton = new Singleton(); //step 2
}
return singleton;
}
}
看上去,这段代码没什么明显问题,但它不是线程安全的。假设当前有N个线程同时调用getInstance()方法,由于当前还没有对象生成,所以一部分同时都进入step 2,
那么就会由多个线程创建多个多个user对象。
解决办法:使用synchronized关键字。经改造上面代码展示如下:
//实现双重校验锁的单例模式【推荐】
class Singleton{
//volatile保证禁止指令重排序,防止另外一个线程返回没有初始化的对象.
private volatile static Singleton instance;
//设置类的构造为私有,防止被其他类创建
private Singleton(){}
public static Singleton instance (){
//第一层屏障,为了防止多次加锁,而造成的性能损耗
if(instance==null){
//step2
//加锁防止多线程
synchronized(Singleton.class){
//第二层屏障,为了防止已经突破了第一层屏障位于step2位置的线程,重现创建对象
if(instance==null){
instance=new Singleton();
}
}
return instance;
}
第一次校验:也就是第一个if(uniqueInstance==null),这个是为了代码提高代码执行效率,由于单例模式只要一次创建实例即可,所以当创建了一个实例之后,再次调用getUniqueInstance方法就不必要进入同步代码块,不用竞争锁。直接返回前面创建的实例即可。说白了假设第一次不检验,看似问题也不大,但是其实这里所用到的思想就如我们在学习hashmap时为什么需要先比较hashcode再比较equals方法,就一句话谁快选谁,这里看似多判断了一次,然而synchronzied同步锁会大大削减效率,开销很大,所以我们就任性地先比较一次,这样如果运气好的话可以通过if语句,跳过synchronized这个步骤。
第二次校验:也就是第二个if(uniqueInstancenull),这个校验是防止二次创建实例,假如有一种情况,当uniqueInstance还未被创建时,线程t1调用getUniqueInstance方法,由于第一次判断if(uniqueInstancenull),此时线程t1准备继续执行,但是由于资源被线程t2抢占了,此时t2页调用getUniqueInstance方法,同样的,由于singleton并没有实例化,t2同样可以通过第一个if,然后继续往下执行,同步代码块,第二个if也通过,然后t2线程创建了一个实例singleton。此时t2线程完成任务,资源又回到t1线程,t1此时也进入同步代码块,如果没有这个第二个if,那么,t1就也会创建一个singleton实例,那么,就会出现创建多个实例的情况,但是加上第二个if,就可以完全避免这个多线程导致多次创建实例的问题。
原文链接:https://blog.csdn.net/weixin_43914278/article/details/104451055
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
volatile:双重检查基础版中由重排序引发的问题
- 做第一次检查的时候,如果lazyDoubleCheckSingleton != null,并不代表lazyDoubleCheckSingleton一定已经初始化完成了,造成这种情形的原因是指令重排序;
- lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton(); 这句在底层经历了3个动作:
1.分配内存给这个对象;
2.初始化对象;
3.设置 lazyDoubleCheckSingleton 指向刚分配的内存;
这3个动作中,2和3的动作可能颠倒,其造成的结果就是:Thread-0第一次检查的时候,由于Thread-1先执行3,lazyDoubleCheckSingleton 指向刚分配的内存,导致Thread-0看到的 lazyDoubleCheckSingleton 不为空,直接返回 lazyDoubleCheckSingleton,但此时lazyDoubleCheckSingleton 在Thread-1中还没有初始化,所以造成程序出问题; - Java规范中有个 intra-thread semantics 的规定,它保证重排序不会改变单线程的执行结果,重排序可以提高程序的执行性能;
重排序问题的解决方案
- 不允许重排序;
- 允许重排序,但不允许另一个线程看到这个重排序;
链接:https://www.jianshu.com/p/b4157c2e8fe5
不允许重排序的解决方案
- 用 volatile 修饰 lazyDoubleCheckSingleton,就禁止了重排序;
- 在多线程的时候,多CPU会共享内存,加了 volatile 后,所有的线程都能看到共享内存的最新状态,保证了内存的可见性;
- 用 volatile 修饰的共享变量在进行写操作的时候,会将当前CPU缓存行的数据写进内存,使得其他CPU缓存了该内存地址的数据无效,从而迫使其他CPU重新从共享内存中获取数据,这样就保证了内存的可见性;
//类的加载过程: //1.加载二进制数据到内存中,生成对应的Class数据结构 //2.连接阶段:验证,准备(给类的静态变量赋默认),解析 //3.初始化(给类的静态变量赋初值) //字节码层面(JIT,CPU对指令都会重新排序) // 正常 重序后 //1.分配空间 1.分配空间 //2.初始化(真值) 2. 引用赋值 //3.引用赋值 3.初始化(真值) //由于s是静态变量,第二步和第三步可能引发问题 }
他最明显的原因是,初始化Helper对象的写操作和对Helper字段的写入不能正常工作。因此,调用getHelper()的线程可以看到对Helper对象的非空引用,但是可以看到Helper对象字段的默认值,而不是构造函数中设置的值。如果编译器内联了对构造函数的调用,那么如果编译器能够证明构造函数不能抛出异常或执行同步,那么初始化对象的写操作和写到助手字段的写入可以自由地重新排序。即使编译器没有重新排序这些写入,在多处理器上,处理器或内存系统也可以重新排序这些写入,就像运行在另一个处理器上的线程所看到的那样。
在给helper对象初始化的过程中,jvm做了下面3件事:
1.给helper对象分配内存
2.调用构造函数
3.将helper对象指向分配的内存空间
由于jvm的"优化",指令2和指令3的执行顺序是不一定的,当执行完指定3后,此时的helper对象就已经不在是null的了,但此时指令2不一定已经被执行。
假设线程1和线程2同时调用getHelper()方法,此时线程1执行完指令1和指令3,线程2抢到了执行权,此时helper对象是非空的。
所以线程2拿到了一个尚未初始化的helper对象,此时线程2调用这个helper就会抛出异常
————————————————
版权声明:本文为CSDN博主「Null_RuzZ」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/null_ruzz/article/details/72530826
2. 饿汉式:初始化时即创建,用到时直接返回
//天生的线程安全
public class Singleton {
//1.私有化构造器
private Singleton(){};
//2.在类中创建一个类的实例,私有化,静态的
private static Singleton instance = new Singleton();
// 3.通过公共方法调用,此公共方法只能类调用,因为设置了 static
public static Singleton getInstance(){
return instance;
}
}
- 静态内部类【推荐】
public class Singleton {
private static class SingletonHolder{
private static final Singleton INSTTANCE = new Singleton();
}
private Singleton(){};
public static final Singleton getInstance(){
return SingletonHolder.INSTTANCE;
}
}