java 设计模式之单例模式

来源:互联网 发布:美国大学统计 知乎 编辑:程序博客网 时间:2024/06/11 03:57
  • 相信很多做java的童鞋都听说过.但是一下子让他们写出来.即便是2-3年工作经验的人,也不一定能写出来一个可用的单例模式.所以我们今天来学习学习单例模式
  • 通常有俩种模式

  • 立即加载-饿汉模式: 立即加载就是使用的时候对象已经实例化

    public class MyObject(){
    private static MyObject = new MyObject();
    …………………….
    }

    • 延时加载-懒汉模式 就是调用get()方法时,对象才被实例

单例模式:

public class SimpleSingle {    /*持有 私有 静态 实例 防止被应用      * -延时加载懒汉模式 就是对象调用时 对象还没有创建      * 立即加载-饿汉模式  = new object()     *      * */    private static SimpleSingle instance = null;    /* 私有构造方法 防止被实例化 */    private SimpleSingle() {    }    /**     * 这里没有同步代码块.所以是线程不安全的     * @return     */    public static SimpleSingle getIntance(){        if(instance == null){            instance = new SimpleSingle();        }        return instance;    }    /**如果该对象用于序列化 可保持 序列化前后一致**/    public Object readResolve(){        return instance;    }    /**     * 线程安全的      * 如果是将synchronized 加到方法上 就是锁了这个对象,效率低下 ,因为只需要在第一次创建的时候加锁 之后就不需要了     *      */     public static synchronized SimpleSingle getInstance1(){         if(instance == null){             instance = new SimpleSingle();         }         return instance;     }    /**     * 使用同步代码块   创建 单例对象     * @return     */    public static SimpleSingle getInstance3(){        if(instance == null){            synchronized (SimpleSingle.class) {                if(instance == null){                instance =  new SimpleSingle();                }            }        }        return instance;    }}

getIntance3() 方法看似没有什么问题
将 synchronized 关键字加在了内部,也就是说当调用的时候是不需要加
锁的,只有在 instance 为 null,并创建对象的时候才需要加锁,性能有一定的提升。但是,这样的情况,
还是有可能有问题的,看下面的情况:在 Java 指令中创建对象和赋值操作是分开进行的,也就是说
instance = new Singleton();语句是分两步执行的。但是 JVM 并不保证这两个操作的先后顺序,也就是
说有可能 JVM 会为新的 Singleton 实例分配空间,然后直接赋值给 instance 成员,然后再去初始化这
个 Singleton 实例。这样就可能出错了,我们以 A、 B 两个线程为例:
a>A、 B 线程同时进入了第一个 if 判断
b>A 首先进入 synchronized 块,由于 instance 为 null,所以它执行 instance = new Singleton();
c>由于 JVM 内部的优化机制, JVM 先画出了一些分配给 Singleton 实例的空白内存,并赋值给 instance
成员(注意此时 JVM 没有开始初始化这个实例),然后 A 离开了 synchronized 块。d>B 进入 synchronized 块,由于 instance 此时不是 null,因此它马上离开了 synchronized 块并将结果
返回给调用该方法的程序。
e>此时 B 线程打算使用 Singleton 实例,却发现它没有被初始化,于是错误发生了。


实际情况是,单例模式使用内部类来维护单例的实现, JVM 内部的机制能够保证当一个类被加载的时
候,这个类的加载过程是线程互斥的。这样当我们第一次调用 getInstance 的时候, JVM 能够帮我们保
证 instance 只被创建一次,并且会保证把赋值给 instance 的内存初始化完毕,这样我们就不用担心上
面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。这样我们
暂时总结一个完美的单例模式:

public class Singleton { /* 私有构造方法,防止被实例化 */ private Singleton() { } /* 此处使用一个内部类来维护单例 */ private static class SingletonFactory { private static Singleton instance = new Singleton(); } /* 获取实例 */ public static Singleton getInstance() { return SingletonFactory.instance; } /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */ public Object readResolve() { return getInstance(); } }

世面上有很多很多的单例模式..但是我还是喜欢自己写的这个双重检测的单例模式 double -check -locking
使用dcl 双重检测来实现单例模式.

/** * 鉴于之前写的多线程单例模式有问题. * 这次我写一个dcl 双重检查 单例模式 * ---doble chek locking */public class MyObject {    //静态实例防止被实例    private volatile static MyObject myObject;    private MyObject() {    }    /**     * 使用双检测机制来解决问题..既不需要同步代码的异步性.     * 也可以保持单例.     * @return     */    public static  MyObject getInstance(){        try {            if (myObject != null) {            }else{//              Thread.sleep(3000);                synchronized (MyObject.class) {                    if(myObject == null){                        myObject = new MyObject();                    }                }            }        } catch (Exception e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        return myObject;    }}