单例的线程安全和如何实现真正的单例

定义:确保某一个类只有一个实例,而且自行实例化并且向整个系统提供这个实例。

也就是 对象不能通过new 来实例化,而是有类本身来实例化一个自己的对象 ,并且创建一个对于这个对象的get方法。

饿汉单例

        

            将类的对象作为类的成员变量,在类声明的时候就已经实例化。
            provate static final objct o = new object();
            public object getInstance(){
                    return o;
            }

懒汉单例


            //在用户第一次调用getInstance的时候进行初始化           
            public object getInstance(){
                    if ( o == null ) {
                        o = new object();
                    }
                    return o;
            }

Double Check Look (DCL) 单例实现模式

     

  public final class Obj{
            private volatile Obj obj ;
            public Obj getInstance(){
                if(obj == null){
                     synchronized(Obj.class){
                            if(obj == null){
                                  obj = new Obj();
                            }
                        }
                }
                return obj;
            }
        }

          .

          等





单例模式 到了最后,各种各样的单例  最终都是在解决 高并发下 线程安全的问题,和实现真正的单例

问题1:线程安全问题, 例如在复杂高并发情况下,get方法在同一时间或者相隔很短时间内调用了两次。那么就会造成 出现两个实例的情况

一般 线程安全的解决办法就是 使用volatile关键字来修饰变量,用synchronized来修饰方法体或一个方法块;


例如:


public class SingleObject{
     private static volatile SingleObject so = null;

    public static SingleObject getInstance(){
              if(so == null ){
                   synchronized(SingleObject.class){
                           if(so == null ){
                                   so = new SingleObject();
                            }
                    }
              }
    }
}

可以看到在成员变量是用volatile修饰,可以保证对象每次都是从主内存中读取。

volatile 或多或少的会影响到性能

而get方法中有两层 判断: 第一层是因为 避免直接的不必要的同步, 第二次则是在同步锁内的判断,这一次则是为了创建实例。

这样写的好处就是 在对象实例化过后,调用get方法 将不会再进行同步锁!



问题2: 序列化,  在java中通过序列化将一个单例 对象 写到磁盘中,然后再读回来,从而有效的获得一个实例。 即使构造方法是私有的,反序列化时依然可以通过特殊的途径去创建类的一个新的实例,相当于调用了该类的构造函数。

            解决办法 :

            一 、使用枚举单例,没错就是枚举 

           

            public enum SingleEnumObject {
                    INSTANCE;
                    public void doSomething(){
                            System.out.println(" class = " +this);
                    }
            }


            枚举在java中与普通的类是一样的,不仅能够有字段,还能够有自己的方法,最重要的是默认枚举单例的创建时线程安全的,并且任何情况下它都是一个单例。

            



注意:

    单例模式一般没有接口,所有扩展起来很难,除非修改代码。

    单例对象如果持有Context对象,很容易造成 内存溢出, 此时需要注意传递给单例对象的Context最好是 Application Context