单例模式

来源:互联网 发布:c语言 char 中文 编辑:程序博客网 时间:2024/05/18 13:08

单例模式属于创建型模式。可以保证一个类仅有一个实例,并提供一个访问它的全局访问点。

优点:
1)在内存中只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例
2)避免对资源的多重占用

饿汉式:

class Singleton {    //类内部实例化一个实例    private static Singleton instance = new Singleton();    //私有构造方法避免被外部使用    private Singleton(){}    //对外提供获取实例的方法    public static Singleton getInstance(){        return instance;    }}

饿汉式单例,在类被加载的时候对象就会被实例化,会造成不必要的损耗。如果这个类被多次调用会造成多次实例化。

有两种解决方法,使用静态内部类,或者使用懒汉式

静态内部类:

class Singleton1 {    private  static class SingleInner {        private static Singleton1 instance = new Singleton1();    }    private Singleton1() {    }    public static  Singleton1 getInstance() {        return SingleInner.instance;    }}

懒汉式:

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

懒汉式单例会存在线程安全问题,在多线程情况下,有可能两个线程同时进入if语句,这样,在两个线程都从if退出时,就创建了两个不一样的对象。

线程安全的懒汉式

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

这种方法,一定程度上保证了线程安全,但是效率低。

双重检验锁

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

上面的例子,通过减小锁的范围,大大提升了效率。通过两重if(instance==null) ,第一重保证只有intance 为null 时,才能进入synchronized代码段;第二重防止出现多个实例。

但仍有小概率出现问题。在解决问题之前,首先说明两个概念:1)原子操作 2)指令重排

原子操作

即不可分割的操作,在计算机中,就是指不会因为线程调度被打断的操作。比如简单的赋值是一个原子操作

m=6 //这是一个原子操作

假如m原先的值为0 ,那么对于这个操作,要么执行成功m变成了6,要么是没执行m还是0,而不会出现m=其他值这种中间态,即使是在并发的线程中

声明并赋值不是一个原子操作

int m=6 //不是原子操作

对于这个语句,至少需要两个操作。
1)声明一个变量n
2)给n赋值为6

这样就会有一个中间状态:变量n已经被声明还没有被赋值的状态。这样在多线程中,由于线程执行顺序的不确定性,如果两个线程都是用m,就有可能导致不稳定的结果出现。

指令重排

计算机为了提高效率,会做一些优化,在不影响最终结果的情况下,可能会对一些语句的执行顺序进行调整。

比如:
int a; //语句1
a=8;//语句2
int b=9;//语句3
int c = a+b;//语句4

正常来说,对于顺序结构,执行的顺序是自上而下,即1234。
但是,由于指令重排的原因,因为不影响最终结果,所以执行顺序可能会变成3124或1324.
由于语句3,4.非原子性操作,3和4也可能会拆分成原子操作,再重排。
也就是说,对于非原子性操作,在不影响最终结果的情况下,其拆分成的原子操作可能会被重新排序执行顺序。

在上面例子里,instance = new Singleton() 不是一个原子操作。JVM 大概做了下面3件事。

1,给instance 分配内存
2,调用Singleton 的构造函数来初始化成员变量,形成实例
3,将instance 对象指向分配的内存空间(执行完此步,instance 非空)

由于JVM的即时编译器中存在指令重排的优化,则第2和第3步的顺序不能保证,若线程1 访问时,第3步先执行,则另一个线程2访问时,instance 非空。直接返回instance,就会出现问题。
问题在于:线程1 对instance的写操作没有完成,线程2就执行了读操作

使用 volatile

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

volatile 关键字一个作用是禁止指令重排,把instance声明为volatile.对它的写操作就会有一个内存屏障,这样在它的赋值完成之前,就不会调用读操作。

枚举实现

enum Singleton6{    INSTSNCE;    public void run(){    }}//调用Singleton6.INSTSNCE.run();

序列化
序列化可能会破坏单例,要想防止序列化对单例的破坏,需要在Singleton类中定义readResolve 方法。

class Singleton7 {    private static volatile Singleton7 instance;    private Singleton7() {    }    public static  Singleton7 getInstance() {        if (instance == null) {            synchronized(Singleton7.class){                if (instance == null) {                    instance = new Singleton7();                }            }        }        return instance;    }    private Object readResolve(){        return instance;    }}
原创粉丝点击