单例模式


第一种 懒汉 线程不安全

public class Singleton {

    private static Singleton instance;

    private Singleton() {
    }
    //静态工厂方法
    public static Singleton getInstance() {

        if (instance == null)
            instance = new Singleton();
        return instance;
    }
}

这种写法lazy loading很明显,等到第一次使用时再创建实例,因此称为懒汉。问题是该写法没有考虑线程安全问题,并发环境下可能会出现多个Singleton实例,在多线程下不能正常工作。但是提供了单例的基本思路:构造器私有。将构造器限制为private能够避免类在外部被实例化。
以下三种是对懒汉模式的改造,保证线程安全

第二种 懒汉 synchronized 保证线程安全

public class Singleton {

    private static Singleton instance;

    private Singleton() {
    }
    //静态工厂方法 采用同步
    public static synchronized Singleton getInstance() {

        if (instance == null)
            instance = new Singleton();
        return instance;
    }
}

该写法能够保证线程安全,但是效率很低。不论instance是否被实例化,当调用getInstance获取实例的时候,都会加锁。

第三种 懒汉 双重校验锁

public class Singleton {

    private volatile static  Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {

        if (instance == null) {//第一次判断
            synchronized (Singleton.class) {
                if (instance == null)//第二次判断
                    instance = new Singleton();
            }
        }

        return instance;
    }
}

1.为何synchronized不加在getInstance方法上,而是移到方法体内?
将synchronized写到方法体内,是为了降低锁的粒度。考虑存在这样的情况:Singleton类中还会存在其他的static方法如f(),当synchronized直接加到getInstance方法上的时候,就会对整个Singleton类加锁,其他程序想要调用类中f(),无法执行只能等待getInstance方法执行完再调用。
将synchronized写进方法内,只有instance实例未被实例化的时候,才会将整个Singleton类进行加锁。所以将synchronized写到方法体内是为了不影响其他方法的调用。
2.为何会进行两次instance==null的判断?去掉第一个可以吗?去掉第二个可以吗?
去掉第一个也可以,但是不推荐。如果去掉第一个if判断,那么当调用getInstance方法的时候,无论instance是否被实例化,都会对整个类进行加锁,不去掉第一个的话,只有instance== null的时候,才会对整个类加锁。
去掉第二个不可以。很可能在第一个if判断的时候,instance== null成立,等到执行到同步块中的时候,instance可能已经被其他线程实例化了。所以需要再加一次判断。
3.关于volatile的作用?
volatile的作用是避免指令的重排序。初始化一个实例:instance= newInstance()在字节码中有四个步骤:
1.申请内存空间
2.初始化默认值
3.执行构造器方法
4.连接引用和实例
这四个步骤后面两个可能会出现重排序,1234或者1243,1243出现的结果就是造成未初始化完全的对象会被发布然后被使用。volatile可以禁止指令的重排序,从而避免这个问题。

第四种 懒汉 静态内部类

public class Singleton {

    private static class LazyHolder {

        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton() {}

    public static final Singleton getInstance() {

        return LazyHolder.INSTANCE;
    }
}

第五种 饿汉 非延迟加载

public class Singleton {
    //加载类的时候会初始化static的instance,
    private static Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}

instance在类装载时就被实例化,因此是天生安全。

第六种 饿汉

public class Singleton {

    private static Singleton instance;

    static {
        instance = new Singleton();
    }

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
}

该种写法与第五种类似,同样都是在类初始化的时候创建静态对象,因此也是线程安全的。

第七种 枚举

public enum Singleton {

    INSTANCE;
}

枚举实现单例模式,不仅可以避免多线程问题,还可以防止反序列化建立新对象。枚举单例模式有点类似饿汉模式,没有实现lazy loading。


饿汉和懒汉的区别:
饿汉保证类一旦加载完成,就把单例实例化,等到getInstance()时,单例已经存在。饿汉天生线程安全,因此可直接用于多线程。
懒汉顾名思义,lazy loading为 延迟加载,等到getInstance()时,再去实例化单例。
懒汉本身不是线程安全的,但是可以通过加同步,静态内部类,双重锁校验的方式,人为实现线程安全。

资源加载的性能:
饿汉模式下当类加载完成后,也完成了单例的实例化,单例就会存在于内存中,不管后面会不会用到,都会占用一定内存。如果该单例占用资源很多的情况下,采用饿汉模式,会导致性能降低。但是由于单例已经初始化完成,因此在第一次调用的时候速度会非常快。
懒汉模式下,延迟加载,对于占用很多资源的单例比较实用。等到使用该单例时再进行加载,会导致性能延迟。

从速度和反应角度看,饿汉模式较优;从资源利用率看,懒汉模式较优。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值