java详解单例模式

简介

单例模式要求某个类只有一个实例变量。只有类提供了一个接口来获得类的唯一实例。所以不允许用户来创建实例。可想而知,类的构造函数只能由类本身来调用。所以类构造函数是private的。因为构造函数是private的。上面提到的获得类的唯一实例的接口肯定是static的。除了基本的要求外。合格的单例模式必须确保多线程下单例类的实例只有一个。
例如在android中ImageLoader应该是单例模式的。来降低资源的消耗

单例模式中的懒汉和饿汉模式

饿汉模式

简单理解是以空间换时间。在声明静态对象时就初始化。

public class SingleTon {
    private static SingleTon singleTon=new SingleTon();
    private SingleTon() {
    }
    public static SingleTon getInstance() {
        return singleTon;
    }
}

该单例模式为什么是线程安全的
饿汉模式的单例static的成员是在类加载的时候就生成了。而不是生成对象的时候。所以是安全的因为类加载之前是不可能生成对象和多线程的

懒汉模式:

先生命静态对象。用户第一次调用getInstance时进行初始化。

public class SingleTon {
    private static SingleTon singleTon;
    private SingleTon() {
    }
    public static synchronized SingleTon getInstance() {
        if(singleTon==null)
        {
            singleTon=new SingleTon();
        }
        return singleTon;
    }
}

注意:这里使用synchronized关键字来保证多线程情况下对象的唯一性。所以懒汉模式每次调用getInstance都会进行同步,会消耗不必要的资源。在使用时在实例化的思想也在一定程度上节约了空间资源。

double check lock(DCL)实现单例

dcl的优点是既能在需要的时候初始化单例,又能保证线程安全,且单例对象初始化后调用getInstance不进行同步锁。如名称,思想是:进行两次检查,查看成员对象是否已经实例化

public class SingleTon {
    private static SingleTon singleTon;
    private SingleTon() {
    }
    public static  SingleTon getInstance() {
        if(singleTon==null)
        {
            synchronized (SingleTon.class)
            {
                if(singleTon==null)
                {
                    singleTon=new SingleTon();
                }
            }
        }
        return singleTon;
    }
}

dcl失效问题

singleTon=new SingleTon();代码会被编译成多条汇编指令。大约一下几件事:
1.给singleTon的实例分配内存
2.调用构造函数,初始化成员字段。
3.将singleTon对象指向分配的内存空间
但是java编译器允许处理器乱序执行。执行顺序可能是1-3-2也可能是1-2-3.如果是前者在3执行完,2未执行,切换到另一个线程。由于已经执行第三条,singleTon非空,该线程直接取走singleTon。就会出现问题。jdk1.5之后只需要将singleTon的定义变成private volatile static SingleTon singleTon;可以保证singleTon对象每次都从主内存中读取。

静态内部类单例模式

public class SingleTon {
    private SingleTon() {
    }
    public static SingleTon getInstance()
    {
        return SingleTonHolder.SINGLE_TON;
    }    
    private static class SingleTonHolder
    {
        private static final SingleTon SINGLE_TON=new SingleTon();
    }
}

特点:既延迟单例的实例化,又保证单例对象的唯一性。由保证线程安全。

枚举单例

public enum SingleTon
{
    INSTANCE;
    public void doSomething()
    {}
}

枚举特点:默认枚举实例的创建时线程安全的。并且在任何情况下都是一个单例。与普通的java类一样,枚举不仅可以有字段还可以有自己的方法。
反序列化:通过序列化,可以将一个单例的实例对象写到磁盘,然后再读取回来。即使构造函数是私有的,反序列化依然可以通过特殊的途径去创建一个类的一个新的实例。反序列化提供一个私有函数readResolve(),让开发人员控制对象的反序列化。所以上面的几种单例方法如果要杜绝对象在反序列化时重新生成对象,必须加入如下代码:

private Object readResolve() throws ObjectStreamExeception
{
    return singleTon;
}

枚举就不会出现这个问题。即使反序列化它也不会重新生成新的实例。

使用容器实现单例模式

public class SingleTon
{
    private SingleTon(){}
    private static Map<String,Object> objMap=new HashMap<String, Object>();
    public static void registerService(String key,Object instance)
    {
        if(!objMap.containsKey(key))
        {
            objMap.put(key,instance);
        }
    }
    public static Object getService(String key)
    {
        return objMap.get(key);
    }
}

总结:不管用哪种方式实现单例模式,核心原理都是将构造函数私有化,并且通过静态方法获取一个唯一的实例。在获取的过程中必须保证线程安全,防止反序列化导致重新生成实例对象。

你可能感兴趣的:(java)