写法"/>
单例模式的四种写法
Node newNode=null;
Node newNode = new Node();
前者,是声明了一个对象(的引用),jvm并没有开辟内存放入一个对象;
而后者,在声明了一个对象的引用后,又把新开辟的没有存储任何有效值的对象的地址赋给了他。
1、懒汉
- 声明一个对象的引用
- 私有化的构造方法
- 判断一下,如果对象为空,再创建对象指向声明的引用
public class Sin { //用的时候构建 lazyprivate static Sin sin;private Sin(){} //其他对象不能创建对象public static synchronized Sin getSin(){if(sin == null){sin = new Sin();}return sin;}
}
缺点:可能会创建多个实例对象,比如,线程1来调用获取单例的方法,判断了对象为空后,线程1挂起;线程2再来调用获取单例的方法,判断对象为空,创建了一个对象,这时候线程1被唤醒,线程1又会创建一个对象。(优化:获取单例方法加synchronized 关键字)
2、饿汉
创建实例时候开始
public class Sin {public static Sin sin = new Sin();private Sin(){};Sin getSin(){return sin;}
}
饿汉式是空间换时间,懒汉式是空间换时间。
3、懒汉模式double-check
public class Sin {private volatile static Sin sin;private Sin(){}public static Sin getSin(){//第一个线程获取了单例的实例对象//后面的线程不需要进入同步代码块了//第一次校验singleton是否为空,提高效率;由于单例模式只需要创建一次if(sin == null){synchronized (Sin.class){ // 锁的是类的class对象if (sin == null)sin = new Sin();}}return sin;}
}
第一次校验:减少锁的竞争。存在对象,直接返回即可。
第二次校验:保证判断和创建对象原子性;不能分开;
- volatile 解决 创建对象时指令重排的问题
因为 singleton = new Singleton() 这句话可以分为三步:
1、为 singleton 分配内存空间;
2、初始化 singleton(给内存空间赋值) 利用singleton的构造方法给成员变量赋值;
3、将 singleton 指向分配的内存空间。(执行完这一步,singleton 就为非空了)
但是由于JVM具有指令重排的特性,第二步和第三步的顺序是不能保证的
最终执行顺序有可能是1-3-2。在多线程下会导致一个线程获得一个未初始化的实例。
线程A执行了1和3,此时线程B调用getInstance()后发现singleton 不为空,因此返回singleton,但是此时的singleton 还没有被初始化,然后使用,顺理成章的报错了。
使用volatile会禁止JVM指令重排,从而保证在多线程下也能正常执行。
4、静态内部类[推荐用]
JVM帮助我们保证了线程的安全性,
在类进行初始化时,别的线程是无法进入的。
对于()方法的调用,也就是类的初始化,虚拟机会在内部确保其多线程环境中的安全性。
public class Sin {//初始化一个静态内部类,内部类有创建final实例对象private static class SinHolder{public static final Sin SIN = new Sin();}private Sin(){};// JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。public static Sin getSin(){return SinHolder.SIN;}}
更多推荐
单例模式的四种写法
发布评论