单列模式之饿汉式,懒汉式

懒汉式的特点是延迟加载,比如配置文件,采用懒汉式的方法,顾名思义,懒汉么,很懒的,配置文件的实例直到用到的
时候才会加载。。。。。。
饿汉式的特点是一开始就加载了,如果说懒汉式是 “时间换空间”,那么饿汉式就是“空间换时间”,因为一开始就创建了实例,所以每次用到的之后直接返回就好了


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.可以使用反射强行调用私有构造器(如果要避免这种情况,可以修改构造器,让他在创建第二个实例的时候抛异常)

而枚举类很好的解决了这两个问题,使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值