单例模式的五种写法:饿汉,懒汉, 双重校验锁,静态内部类,枚举

1. 饿汉

//线程安全
public class SingletonClass{
    //【静态常量,可用】
    private static final SingtonClass instance = new SingletonClass();
    private SingletonClass(){}
    pubilc static SingletonClass getInstance(){
        return instance;
    }
}
public class SingletonClass{
   //【静态代码块,可用】
    private SingtonClass instance = null;
    static{
        instance = new SingletonClass();
    }
    private SingletonClass(){}
    pubilc static SingletonClass getInstance(){
        return instance;
    }
}

分析:基于classloder机制避免了多线程的同步问题,
单例在加载时已经实例化,此时线程还没有启动,所以天生的线程安全,
无论这个类是否被使用,都会被常见一个instance。

2,懒汉

// 线程不安全
public class SingletonClass{
    //【线程不安全,不可用】
    private static SingletonClass instance = null;
    private SingletonClass(){}
    public static SingletonClass getInstance(){
        if(instance == null){
            //多线程时,在该处时间片切换,会发生错误;
            instance = new SingletonClass();
        }
        return instance;
    }
}

分析:线程A希望使用SingletonClass,调用getInstance()方法。因为是第一次调用,A就发现instance是null的,于是它开始创建实例,就在这个时候,CPU发生时间片切换,线程B开始执行,它要使用SingletonClass,调用getInstance()方法,同样检测到instance是null(注意,这是在A检测完之后切换的,也就是说A并没有来得及创建对象)因此B开始创建。B创建完成后,切换到A继续执行,因为它已经检测完了,所以A不会再检测一遍,它会直接创建对象。这样,线程A和B各自拥有一个SingletonClass的对象——单例失败!

//线程安全 
public class SingletonClass{
        //【线程安全,不推荐使用】
    private static SingletonClass instance = null;
    private SingletonClass(){}
    public synchronized static SingletonClass getInstance(){
        if(instance==null){
            instance = new SingletonClass();
        }
        return instance;
    }
    //public static SingletonClass getInstance(){
    //    synchronized(SingletonClass.class){
    //        if(instance==null){
    //            instance = new SingletonClass();
    //        }                
    //    }
    //   return instance;
    //}

}

分析:sychronized对整个方法进行加锁,严重影响程序性能。

3.双重校验锁

public class SingletonClass{
    //【推荐使用】    
    private volatile static SingletonClass instance = null;
    public static SingletonClass getInstance(){
        //double-check
        if(instance==null){
            synchronized(SingletonClass.class){
                            if(instance==null)
                    instance = new SingltonClass();
            }
        }
        return instance;
    }
    private SingletonClass(){}
}

分析:所谓编译,就是把源代码“翻译”成目标代码(大多数是指机器代码)的过程。针对Java,它的目标代码不是本地机器代码,而是虚拟机代码。编译原理里面有一个很重要的内容是编译器优化。所谓编译器优化是指,在不改变原来语义的情况下,通过调整语句顺序,来让程序运行的更快。这个过程成为reorder。创建一个变量需要哪些步骤呢?

1. 申请一块内存,调用构造方法进行初始化操作,
2. 分配一个指针指向这块内存。

 这两个操作谁在前谁在后呢?JVM规范并没有规定。那么就存在这么一种情况,JVM是先开辟出一块内存,然后把指针指向这块内存,最后调用构造方法进行初始化。
 下面我们来考虑这么一种情况:线程A开始创建SingletonClass的实例,此时线程B调用了getInstance()方法,首先判断instance是否为null。按照我们上面所说的内存模型,A已经把instance指向了那块内存,只是还没有调用构造方法,因此B检测到instance不为null,于是直接把instance返回了——问题出现了,尽管instance不为null,但它并没有构造完成,就像一套房子已经给了你钥匙,但你并不能住进去,因为里面还没有收拾。此时,如果B在A将instance构造完成之前就是用了这个实例,程序就会出现错误了!

4.静态内部类

//线程安全,不受JDK版本影响,推荐使用
public class SingletonClass{
        //【推荐使用】
    private static class SingletonClassInstance{
        private static final SingletonClass instance = new SingletonClass();
    }
    public static SingletonClass getInstance(){
        return SingletonClassInstance.instance;
    }
    private SingletonClass(){}
}

分析:这一版本的单例模式实现代码中,使用了Java的静态内部类。这一技术是被JVM明确说明了的,因此不存在任何二义性。在这段代码中,因为SingletonClass没有static的属性,因此并不会被初始化。直到调用getInstance()的时候,会首先加载SingletonClassInstance类,这个类有一个static的SingletonClass实例,因此需要调用SingletonClass的构造方法,然后getInstance()将把这个内部类的instance返回给使用者。由于这个instance是static的,因此并不会构造多次。由于SingletonClassInstance是私有静态内部类,所以不会被其他类知道,同样,static语义也要求不会有多个实例存在。并且,JSL规范定义,类的构造必须是原子性的,非并发的,因此不需要加同步块。同样,由于这个构造是并发的,所以getInstance()也并不需要加同步。

5.枚举

public enum SingletonClass{
    //【推荐使用】
    INSTANCE;
    public void method(){}
}

分析:这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象.由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。

注意
1)如果单例由不同的类加载器装入,那便可能存在多个单例类的实例。
2)如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和反序列化,可能存在多个单例类的实例。
对第一个问题修复的办法是:

private static Class getClass(String classname) throws ClassNotFoundException {     
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();     
        if(classLoader == null)
            classLoader = Singleton.class.getClassLoader();     
        return (classLoader.loadClass(classname));        
 } 

对第二个问题修复的办法是:

 public class Singleton implements java.io.Serializable {     
    public static Singleton INSTANCE = new Singleton();     
    protected Singleton() { }     
    private Object readResolve() {     
        return INSTANCE;     
    }    
} 

参考链接:
http://devbean.blog.51cto.com/448512/203501/
http://www.blogjava.net/kenzhh/archive/2013/03/15/357824.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值