单例模式

单例模式是我们用的最多但错的最多的一种设计模式。

单例模式主要分为饿汉式和懒汉式

饿汉式(最简单)

public class Singleton {
    private static Singleton singleton = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return singleton;
    }
}

懒汉式(最易出错的)

下边算是推导过程

public class Singleton {
    private static Singleton singleton = null;
    private Singleton() {}
    public static Singleton getInstance() {     
        if (singleton == null) {
            singleton = new Singleton();               // 1
        }
        return singleton;
    }
}

问题:多线程程序中会生成多个实例
原因:在多线程程序中,有两个线程A和B同时执行到 if 条件判断语句,A判断singleton为空准备执行1时让出了CPU时间片,B也判断singleton为空,接着执行1,此时创建了一个实例对象;A获取了CPU时间片后接着执行1,也创建了实例对象,这就导致多个单例对象的情况。
措施:加上同步锁

public class Singleton {
	private static Singleton singleton = null;
    private Singleton() {}
    public static synchronized Singleton getInstance() {     
        if (singleton == null) {
            singleton = new Singleton();               // 1
        }
        return singleton;
    }
}

问题:解决了多线程并发的问题,但是却带来了效率问题
原因:我们的目的是只创建一个实例,即1处代码只会执行一次,也正是这个地方才需要同步,后面创建了实例之后,singleton非空就会直接返回对象引用,而不用每次都在同步代码块中进行非空验证。
措施:可以考虑只对1处进行同步:

public class Singleton {
    private static Singleton singleton = null;
    private Singleton() {}
    public static Singleton getInstance() {     
        if (singleton == null) {
            synchronized (Singleton.class) {
                singleton = new Singleton();               // 1
            }
        }
        return singleton;
    }
}

问题:多线程程序中,会创建多个实例。
原因:两个线程同时经过 if 判断之后,其中线程A进入synchronized代码块创建一个实例并return,此时线程B中的singleton 进入synchronized代码块中又进行了一次实例化,从而产生了多个实例。
措施:可以在后续的线程中做第二次非空检查。也就是所谓的双重检测:

public class Singleton {
    private static Singleton singleton = null;
    private Singleton() {}
    public static Singleton getInstance() {     
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();               // 1
                }
            }
        }
        return singleton;
    }
}

问题:这种双重检测机制在JDK1.5之前是有问题的,还是出在1,由所谓的无序写入造成的。
原因:无序写入对应到1就是singleton已经不是null,而是指向了堆上的一个对象,但是该对象却还没有完成初始化动作。当后续的线程发现singleton不是null而直接使用的时候,就会出现意料之外的问题。
措施:JDK1.5之后,可以使用volatile关键字修饰变量来解决无序写入产生的问题,因为volatile关键字的一个重要作用是禁止指令重排序,即保证不会出现内存分配、返回对象引用、初始化这样的顺序,从而使得双重检测真正发挥作用。

public class Singleton {
    private static volatile Singleton singleton = null;
    private Singleton() {}
    public static Singleton getInstance() {     
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();               // 1
                }
            }
        }
        return singleton;
    }
}

嵌套类的写法

public class Singleton {
    private Singleton() {}
    // 主要是使用了 嵌套类可以访问外部类的静态属性和静态方法 的特性
    private static class Holder {
        private static Singleton instance = new Singleton();
    }
    public static Singleton getInstance() {
        return Holder.instance;
    }
}

嵌套类最经典,推荐使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值