Java设计模式——单例模式

1.饿汉式:静态常量html

这种方法很是的简单,由于单例的实例被声明成static和final变量了,在第一次加载类到内存中时就会初始化,因此会建立实例自己是线程安全的。安全

public class Singleton1 {  

    private final static Singleton1 instance = new Singleton1();  
    private Singleton1(){}  
    public static Singleton1 getInstance(){  
        return instance;  
    }  
}

它的缺点是否是一种懒加载,单例会在加载类后一开始就被初始化,即便客户端没有调用getInstance()方法。饿汉式的建立方式在一些场景中将没法使用:好比Singleton实例的建立是依赖参数或者配置文件的,在getInstance()以前必须调用某个方法设置参数给它,那么单例写法就没法使用了。
2.懒汉式:线程不安全多线程

public class Singleton3 {  

    private static Singleton3 instance ;  
    private Singleton3(){}  
    public  static Singleton3 getInstance(){  
        if(instance == null){  
            instance = new Singleton3();  
        }  
        return instance;  
    }  
}

这里使用了懒加载模式,可是却存在致命的问题。当多个线程并行调用getInstance()的时候,就会建立多个实例,即在多线程下不能正常工做。
3.懒汉式:线程安全ide

public class Singleton4 {  

    private static Singleton4 instance;  
    private Singleton4(){}  
    public static synchronized Singleton4 getInstance(){  
        if(instance == null){  
            instance = new Singleton4();  
        }  
        return instance;  
    }  
}```
虽然作到了线程安全,而且解决了多实例的问题,可是它并不高效。由于在任什么时候候只能有一个线程调用getInstance()方法,可是同步操做只须要在第一次调用时才被须要,即第一次建立单例实例对象时。
4.懒汉式:静态内部类

public class Singleton5 { 函数

private static class SingletonHandler{  
    private static final Singleton5 INSTANCE = new Singleton5();  
}  
private Singleton5(){}  
public static Singleton5 getInstance(){  
    return SingletonHandler.INSTANCE;  
}

}```
这种写法仍然使用JVM自己机制保证了线程安全问题;因为SingletonHandler是私有的,除了getInstacne()以外没有办法访问它,所以它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷,也不依赖JDK版本。
5.双重检验锁:性能

双重检验模式,是一种使用同步块加锁的方法。又称其为双重检查锁,由于会有两次检查instance == null,一次是在同步块外,一次是在同步快内。为何在同步块内还要检验一次,由于可能会有多个线程一块儿进入同步块外的if,若是在同步块内不进行二次检验的话就会生成多个实例了。优化

public class Singleton6 {  

    private static Singleton6 instance;  
    private Singleton6(){}  
    public static Singleton6 getSingleton6(){  
        if(instance==null){  
            synchronized(Singleton6.class){  
                if(instance==null){  
                   instance = new Singleton6();  
                }  
            }  
        }  
        return instance;  
    }  

}```
其中,instance = new Singleton6()并不是是原子操做,事实上在JVM中这句话作了三件事:
1.给instance分配内存

2.调用Singleton6的构造函数来初始化成员变量

3.将instance对象指向分配的空间(执行完这一步instance就为null)

可是在JVM的即时编译器中存在指令重排序的优化,也就是说上面的第二步和第三步是不能保证顺序的,最终执行的顺序多是1-2-3或者是1-3-2。若是是后者,则在3执行完毕,2执行以前,被线程2抢占了,这时instance已是非null了(但却没有初始化),因此线程2会直接返回instance,而后使用,而后会报错。

```public class Singleton6 {  

    private volatile static Singleton6 instance;  
    private Singleton6(){}  
    public static Singleton6 getSingleton6(){  
        if(instance==null){  
            synchronized(Singleton6.class){  
                if(instance==null){  
                   instance = new Singleton6();  
                }  
            }  
        }  
        return instance;  
    }  

}

有人认为使用volatile的缘由是可见性,也就是能够保证线程在本地不会存有instance副本,每次都是去主内存中读取,可是实际上是不对的。使用volatile的主要缘由是其另外一个特性:禁止指令重排序优化。也就是说,在volatile变量的赋值操做后面会有一个内存屏障(生成的汇编代码上),读操做不会被重排序到内存屏障以前。好比上面的例子,取操做必须在执行完1-2-3以后或者1-3-2以后,不存在执行到1-3而后取到值的状况。从[先行发生原则]的角度理解的话,就是对于一个volatile变量的写操做都先行发生于后面对这个变量的读操做。
注意:在Java5之前的版本使用了volatile的双检锁仍是有问题的。其缘由是Java5之前的JMM(Java内存模型)是存在缺陷的,即时将变量声明成volatile也不能避免重排序。线程

6.枚举:code

public class Singleton7 {  

    public enum EasySingleton{  
        INSTANCE;  
    }  
}

咱们能够经过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。建立枚举默认就是线程安全,并且还能防止反序列化致使从新建立新的对象。htm

更多干货教程地址:http://edu.51cto.com/lecturer/12509664.html和关注本帐号。

相关文章
相关标签/搜索