单例模式详解

目录

单例模式

单例模式的定义

单例模式的实现步骤

同步和异步的概念

饿汉式-线程安全

懒汉式-线程不安全

懒汉式-同步锁-线程安全

双重校验锁-线程安全(推荐使用)

volatile关键字:

静态内部类(推荐使用)

枚举实现(最佳推荐使用)


单例模式

单例模式的定义

  • 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个获取该对象实例的方法。

  • 目的:为了节省内存资源,保证数据内容的一致性

单例模式的实现步骤

  1. 构造方法私有化(防止外部类创建该类的对象)

  2. 类的内部创建一个静态私有对象实例

  3. 向外提供一个静态的共有函数用于获取该静态私有实例

同步和异步的概念

  • 同步:体现了排队的效果,同一时刻只能有一个线程独占资源,其它没有权限的线程需要进行排队。坏处就是效率降低,但是保证了线程安全。

  • 异步:体现了多线程抢占资源的效果,线程间互相不等待,互相抢占资源。坏处就是有安全隐患,但是效率要高一些。

饿汉式-线程安全

  • 原理:依赖JVM类加载机制,保证单例(一个对象实例)只被创建一次

  • 优点:线程安全、初始化速度快、占用内存小

  • 缺点:可能会造成内存浪费--------如果从始至终从未使用过这个实例,则会造成内存的浪费。

public class Singleton1 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode()="+instance1.hashCode());
        System.out.println("instance2.hashCode()="+instance2.hashCode());
    }
}
class Singleton {
    //构造器私有化,防止外部new
    private Singleton() {
​
    }
    
    private static Singleton instance = new Singleton();
    
    public static Singleton getInstance() {
        returninstance;
    }
​
}

img

静态代码块饿汉式(和上面方式类似,优缺点上同)

public class Singleton1 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode()="+instance1.hashCode());
        System.out.println("instance2.hashCode()="+instance2.hashCode());
    }
}
class Singleton {
    //构造器私有化,防止外部new
    private Singleton() {
​
    }
    
    private static Singleton instance;
    
    //静态代码块,最先执行,且只执行一次
    static {
         instance = new Singleton();
    }
​
    public static Singleton getInstance() {
        return instance;
    }
​
}

img

懒汉式-线程不安全

  • 原理:类加载时,先不自动创建单例;需要时才创建单例

  • 优点:按需加载单例;节约内存

  • 缺点:多线程下不安全

这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 if (instance == null) ,并且此时 instance 为 null,那么会有多个线程执行 instance = new Singleton(); 语句,这将导致多次实例化 instance。

public class Singleton1 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode()="+instance1.hashCode());
        System.out.println("instance2.hashCode()="+instance2.hashCode());
    }
}
class Singleton {
    //构造器私有化,防止外部new
    private Singleton() {
​
    }
​
    private static Singleton instance;
​
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
​
}

img

懒汉式-同步锁-线程安全

  • 原理:只需要对 getInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了多次实例化 instance 的问题。

  • 优点:线程安全;节约内存

  • 缺点:效率低------当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,因此性能上有一定的损耗。

public class Singleton1 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode()="+instance1.hashCode());
        System.out.println("instance2.hashCode()="+instance2.hashCode());
    }
}
class Singleton {
    //构造器私有化,防止外部new
    private Singleton() {
​
    }
​
    private static Singleton instance;
​
    //synchronized同步锁
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
​
}

img

双重校验锁-线程安全(推荐使用)

  • 原理:

    • 校验锁1:若单例已创建,则直接返回已创建的单例,无需再执行加锁操作

    • 校验锁2:防止多次创建单例问题

  • 优点:线程安全;节省资源(不需过多的同步开销,同步锁=耗时、耗能);效率高

  • 缺点:实现复杂(多种判断,易出错)

volatile关键字:

instance 采用 volatile 关键字修饰也是很有必要的。instance = new Singleton(); 这段代码其实是分为三步执行:

  1. 分配内存空间

  2. 初始化对象

  3. 将 instance 指向分配的内存地址

但是由于 JVM 具有指令重排的特性,有可能执行顺序变为了 1>3>2,这在单线程情况下自然是没有问题。但如果是多线程下,有可能获得是一个还没有被初始化的实例,以致于程序出错。使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。

public class Singleton1 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode()="+instance1.hashCode());
        System.out.println("instance2.hashCode()="+instance2.hashCode());
    }
}
class Singleton {
    //构造器私有化,防止外部new
    private Singleton() {
​
    }
​
    //volatile保证可见性,防止指令重排
    private static volatile Singleton instance;
​
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
​
}

img

静态内部类(推荐使用)

  • 原理:

    • 按需加载:在静态内部类里创建单例,在装载该内部类时才会去创建单例

    • 线程安全:类是由JVM加载,而JVM只会加载一遍,保证只有一个单例

  • 优点:线程安全、节省资源(不需过多的同步开销)、实现简单

  • 缺点:没有缺点

​
public class Singleton1 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode()="+instance1.hashCode());
        System.out.println("instance2.hashCode()="+instance2.hashCode());
    }
}
class Singleton {
    //构造器私有化,防止外部new
    private Singleton() {
​
    }
    private static class SingletonInstance {
        private static final Singleton INSTENCE = new Singleton();
    }
​
    public static Singleton getInstance() {
        return SingletonInstance.INSTENCE;
    }
}


​

img

枚举实现(最佳推荐使用)

这是单例模式的最佳实践,它实现简单,并且在面对复杂的序列化或者反射攻击的时候,能够防止被实例化多次。

  • 原理:

    • 枚举类型 = 不可被继承的类(final)

    • 每个枚举元素 = 类静态变量 = 依赖JVM类加载机制,保证单例只被创建一次

    • 枚举元素 都通过静态代码块来进行初始化

    • 构造方法访问权限默认 = 私有(private)

    • 大部分方法都是final

  • 优点:线程安全、自由序列化、实现更加简单、简洁

  • 缺点:没有缺点

​
public class Singleton1 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
        System.out.println(instance1 == instance2);
​
        System.out.println("instance1.hashCode()="+instance1.hashCode());
        System.out.println("instance2.hashCode()="+instance2.hashCode());
​
        instance1.sayOk();
    }
}
//使用枚举实现单例
 enum Singleton {
    INSTANCE; //属性
    public void sayOk() {
        System.out.println("ok");
    }
}


​

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值