懒汉式的特点是延迟加载,比如配置文件,采用懒汉式的方法,顾名思义,懒汉么,很懒的,配置文件的实例直到用到的
时候才会加载。。。。。。
饿汉式的特点是一开始就加载了,如果说懒汉式是
“时间换空间”,那么饿汉式就是“空间换时间”,因为一开始就创建了实例,所以每次用到的之后直接返回就好了
1.恶汉式
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {
}
private static Singleton newInsance() {
return singleton;
}
}
从上面的代码中可以看见,类的构造方法是用
private(私有化)定义的,那么其他类不能实例化此类,并且提供一个静态的实例并用静态方法返回给调用者,饿汉模式只不过是最简单的一种实现方式,饿汉模式在类加载的时候就对实例进行创建,实例在整个程序周期都存在,好处是只在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题,它的缺点也很明显,即使这个类没有用到也会被创建,而且在类加载之后就被创建,内存就会被浪费。所以饿汉模式的使用场景是在这个类在初始化的时候就会被用到,并且单例模式占用内存小,如果在某个特定情景下才会被用到那就不合适了。
2.懒汉式
public class Singleton {
private static Singleton singleton = null;
private Singleton() {
}
public static Singleton newInstance() {
if (null == singleton) {
singleton = new Singleton();
}
return singleton;
}
}
饿汉式和懒汉式的基本实现效果是一样的,只不过懒汉式比饿汉式多了一层判断,如果这个单例模式被调用,并且单利模式没有被
new,就会进入if()中,如果已经存在那么就返回给调用者,这就解决了饿汉式的问题,但是懒汉式他的线程是不安全的,当多线程同时调用他的newInteger()方法,那么他就有可能创建出多个来(想不明白,怎么学的线程),所以就需要给它加锁来解决这个问题。
3.懒汉式(微调版)
public class Singleton {
private static Singleton singleton = null;
private Singleton() {
}
public static synchronized Singleton newInstance() {
if (null == singleton) {
singleton = new Singleton();
}
return singleton;
}
}
它比上面的懒汉式多加了个
synchronized 的修饰符,不明白??多线程。
微调版的懒汉式看起来及解决了线程的并发问题,有实现了延迟加载,但是它存在着性能问题,因为
synchronized修饰的同步方法比一般方法要慢的多,而且多次调用newInstance()方法也就多次调用了synchronized修饰符,累积的性能损耗就比较大,所以就有了二次升级版本
4.懒汉式(二次升级)
public class Singleton {
private static Singleton singleton = null;
private Singleton() {
}
public static Singleton newInstance() {
if (null == singleton) {
synchronized(Singleton.class){
if (null==singleton){
singleton = new Singleton();
}
}
}
return singleton;
}
}
可以看上面在同步代码块外多了一层对象为空的判断,由于单例对象只需要常见一次,如果后面再次调用此方法只需要直接返回单例对象,因此,大部分情况下,调用上面的方法都不会执行到同步代码块,从而提高了程序性能,不过还有多一点,线程问题,假若当
A,B两条线程同时执行到了if(null==singleton),A,B都会认为单例对象没有创建,然后通过线程锁,创建出两个单例对象???所以就要在线程锁之后再次确认单例对象是否为空,还是上面的A,B流程,当A经过了第一个if判断和synchronized()后还没有出来解锁时,B也通过了第一个if()判断,因为A线程一个通过了线程锁,所以在A还没出来前是不能进入的,当A解锁后,B进入线程锁,当进行第二次if()判断时,单例对象已经被A线程创建了,在项目进程结束前再也不会进入第一个if()判断
5.懒汉式(三次升级)
现在看来双重校验锁即实现了延迟加载,又解决了线程的并发问题,同时还解决了执行效率问题,但是还有个指令重排优化,即指在不改变原语义的情况下,通过调整指令的执行顺序让程序运行的更快,
JVM并没有规定编译器优化相关的内容,也就是说JVM可以自由的进行指令重排序的优化。
这个问题的关键就在于由于指令重排优化的存在,导致初始化
Singleton和将对象的地址赋给instance字段的顺序是不正确的。在某个线程创建的单例对象时,在构造方法被调用之前,就为给对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化,若紧接着另外一个线程来调用getInstance,取到的基石状态不正确的对象,程序就会出错。
好在
JDK1.5之后版本增加了volatile关键字。Volatile的一个语义是禁止指令重排序优化,也就能保证instance变量被赋值的时候对象已经初始化了,
public class Singleton {
private static volatile Singleton singleton = null;
private Singleton() {}
public static Singleton newInstance() {
if (null == singleton) {
synchronized(Singleton.class){
if (null==singleton){
singleton = new Singleton();
}
}
}
return singleton;
}
}
6.静态内部类
这种方法同样利用了类加载机制来保证只创建了个
instance实例,他和饿汉式一样,也是利用了类加载机制,一次不存在多线程并发问题,不一样的是,他是内部类里面去创建的对象实例。这样的话。只要应用中不适用内部类,JVM就不会加载这个单例类,也就不会常见单例对象,从而实现懒汉式的延迟加载,也就是同时保证了延迟加载和线程安全。
public class Singleton {
private static class SingletonHolder{
public static Singleton singleton = new Singleton();
}
public Singleton() {
}
public static Singleton newInstance(){
return SingletonHolder.singleton;
}
}
7.枚举
public enum Singleton {
singleton;
private void whateverMethod(){};
}
前面实现单例的方式都有那些共同的缺点,
(1.需要额外的工作来实现序列化,否则每次反序列化一个序列化的对象时都会创建一个新的实例。
(2.可以使用反射强行调用私有构造器(如果要避免这种情况,可以修改构造器,让他在创建第二个实例的时候抛异常)
而枚举类很好的解决了这两个问题,使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。