单例模式

单例模式

定义:单例模式顾名思义就是只有一个对象被创建,大家全部共享这个对象

分类:常见的单例模式分为饿汉式、懒汉式、双重校验锁、静态内部类枚举五种

单例模式详解

共同点:构造方法都是私有的,这样做保证其他类不能实例化此类

一、饿汉式

场景:适用于单例占用内存较小,在初始化时就会被用到

缺点:单例没有用到也会被创建,而且在类加载之后就被创建,内存浪费

demo示例:

/**
 * 饿汉式单例
 *
 * @author supu
 * @date 2019-06-20 14:35
 **/
public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {

    }

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

二、懒汉式

场景:适用于按需创建单例

缺点:存在性能问题,直接加锁后多线程访问执行效率低

demo示例:

/**
 * 懒汉式单例
 *
 * @author supu
 * @date 2019-06-20 14:35
 **/
public class Singleton {
    private static Singleton instance;

    private Singleton() {

    }

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

三、双重校验锁

    Java中的指令重排优化。所谓指令重排优化是指在不改变原语义的情况下,通过调整指令的执行顺序让程序运行的更快。JVM中并没有规定编译器优化相关的内容,也就是说JVM可以自由的进行指令重排序的优化。

   这个问题的关键就在于由于指令重排优化的存在,导致初始化Singleton和将对象地址赋给instance字段的顺序是不确定的。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错

优点:使用volatile关键字禁止指令重排序优化,保证了instance变量被赋值的时候对象已经是初始化的。即实现了延迟加载,又解决了线程并发问题,同时还解决了执行效率问题

demo示例:

/**
 * 双重校验锁单例
 *
 * @author supu
 * @date 2019-06-20 14:35
 **/
public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {

    }

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

四、静态内部类

优点:利用类加载机制(避免了线程并发问题)来保证只创建一个实例,使用内部类时才会创建单例,这样达到了延迟加载的效果

demo示例:

/**
 * 静态内部类单例
 *
 * @author supu
 * @date 2019-06-20 14:35
 **/
public class Singleton {
    private static class SingletonHolder {
        public static Singleton instance = new Singleton();
    }

    private Singleton() {

    }

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

注意,上述四种方法有以下两个共同的缺点:

1、需要额外的工作来实现序列化,否则每次反序列化一个序列化的对象时都会创建一个新的实例

2、可以使用反射强行调用私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛出异常)

原因:单例一旦实现了序列化接口,那么它们就不在保持单例了,因为readObject()方法一直返回一个新的对象(就像java的构造方法一样)

解决方法:你可以使用readResolve()方法来避免此事发生,实例如下:

但是如果你的单例类中还引用其他对象的状态的话,就需要使用transient关键字修饰引用对象

五、枚举

优点:枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象

demo示例:

/**
 * 枚举单例
 *
 * @author supu
 * @date 2019-06-20 14:35
 **/
public enum Singleton {
    INSTANCE;

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

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值