Java单例模式写法

简介: Java单例模式写法

单例模式


单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例.

为什么要保证只存在一份对象呢?

因为有些对象管理的内存数据可能会很多, 可能有些项目里就一个对象运行起来就吃上百G的内存空间, 如果不小心多new了几个, 那系统可能直接崩溃了.


饿汉模式实现单例


类加载的同时, 创建实例.


class Singleton {
    private static Singleton singleton = new Singleton();
    private Singleton() {}    //不允许外部调用构造方法
    public static Singleton getSingleton() {
        return singleton;  //将创建好的实例返回
    }
}


这里只是单纯的读操作, 因此该模式是线程安全的.


懒汉模式实现单例


核心思想 :非必要不创建.

加载的时候不创建实例. 第一次使用的时候才创建实例.


单线程版


class Singleton {
    private static Singleton singleton = null;
    private Singleton() {}  //修改了构造方法的访问修饰权限符, 只有在类内部才能访问构造方法
    public static Singleton getSingleton() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}


上面的懒汉模式实现是线程不安全的.

为什么不安全呢?

比如两个线程同时调用 getSingleton 方法时, 此时 singleton 还为空, t1 线程和 t2 线程都走进了判断语句, 判断通过, 它两都 new 出了对象, 这与我们预期的创建一个对象不符, 所以线程不安全.


我们可以通过加锁来解决这一问题, 下面是多线程版 , 线程安全.


多线程版


怎么加锁呢?

看看这样加锁是否可行:


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

显然这样是不行的, 当两个线程同时调用 getSingleton 方法时, 此时 singleton 还为空, t1 线程和 t2 线程都走进了判断语句, 判断通过, 然后通过竞争锁, 竞争成功的线程先 new 对象, 另一个线程后 new 对象, 这也是 new 了多个对象, 与预期不符.


我们再来看看给方法加锁:


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


当多个线程同时调用 getSingleton 方法时, 通过竞争锁, 竞争成功的线程先进去创建完对象出来后, 其他线程再来获取对象就不会再创建对象了.


其实这里还有一个问题, 那就是指令重排序问题, 什么意思呢?

举个例字 :

假设我们有两个线程 t1 和 t2,

t1 有个操作是: s = new Student();

t2 有个操作是: if(s != null) { s.learn(); }


这个操作 : s = new Student(); 大体可以分为三个步骤:


  1. 申请内存空间
  2. 调用构造方法(初始化内存的数据)
  3. 把对象的引用赋值给 s (内存地址的赋值)


如果是单线程, 此处进行指令重排序,步骤2和步骤3是可以调换顺序的, 重排序后可能就是132执行了, 这对单线程结果没影响, 但多线程就不行了.


回到上面的 t1 和 t2, 如果 t1 的操作进行指令重排序, 就会先申请内存, 然后把这个内存地址赋值给 s (注意这里还没有调用构造方法, 没有new对象), 这时 s 不是 null , 这个时候如果线程 t2 刚好进行 if 判断则会直接进入, 然后调用 s 的方法, 因为 s 里没对象, 就会抛出空指针异常.


这也是上面给方法加锁代码存在的问题, 如果得到的 singleton 里没对象就调用这里面的方法, 那就会产生空指针异常.


如何避免发生指令重排序呢?

我们可以加个 volatile 来禁止指令重排序, 在下面代码中体现.


多线程版优化


class Singleton {
    private volatile static Singleton singleton; //禁止对singleton进行指令重排序
    private Singleton() {}
    public static Singleton getSingleton() {
        //注意这里有两个 if 判断是否为空!!! 
        if(singleton == null) {
            synchronized (Singleton.class) {
                if(singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}


为什么要两个 if 判断呢?

加锁 / 解锁是一件开销比较高的事情. 而懒汉模式的线程不安全只是发生在首次创建实例的时候.因此后续使用的时候, 不必再进行加锁了.

这样可以让加锁操作只在第一次创建实例的时候出现.


小结


单例模式线程安全问题 :


  1. 饿汉模式, 天然就是安全的, 只是读操作.
  2. 懒汉模式, 不安全的, 有读也有写.


如何将懒汉模式变安全:


  1. 加锁, 把 if new 变为原子操作.
  2. 双重 if, 减少不必要的加锁操作.
  3. 使用 volatile 禁止指令重排序, 保证后续线程拿到的是完整对象.

相关文章
|
3月前
|
设计模式 安全 Java
Java编程中的单例模式深入剖析
【10月更文挑战第21天】在Java的世界里,单例模式是设计模式中一个常见而又强大的存在。它确保了一个类只有一个实例,并提供一个全局访问点。本文将深入探讨如何正确实现单例模式,包括常见的实现方式、优缺点分析以及最佳实践,同时也会通过实际代码示例来加深理解。无论你是Java新手还是资深开发者,这篇文章都将为你提供宝贵的见解和技巧。
107 65
|
2月前
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
40 4
|
2月前
|
设计模式 安全 Java
Java编程中的单例模式:理解与实践
【10月更文挑战第31天】在Java的世界里,单例模式是一种优雅的解决方案,它确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的实现方式、使用场景及其优缺点,同时提供代码示例以加深理解。无论你是Java新手还是有经验的开发者,掌握单例模式都将是你技能库中的宝贵财富。
59 2
|
2月前
|
设计模式 安全 Java
Java编程中的单例模式深入解析
【10月更文挑战第31天】在编程世界中,设计模式就像是建筑中的蓝图,它们定义了解决常见问题的最佳实践。本文将通过浅显易懂的语言带你深入了解Java中广泛应用的单例模式,并展示如何实现它。
|
2月前
|
设计模式 SQL 安全
Java编程中的单例模式深入解析
【10月更文挑战第24天】在软件工程中,单例模式是设计模式的一种,它确保一个类只有一个实例,并提供一个全局访问点。本文将探讨如何在Java中使用单例模式,并分析其优缺点以及适用场景。
18 0
|
3月前
|
SQL 设计模式 Java
[Java]单例模式
本文介绍了单例模式的概念及其实现方式,包括饿汉式和懒汉式两种形式,并详细探讨了懒汉式中可能出现的线程安全问题及其解决方案,如锁方法、锁代码块和双重检查锁(DCL)。文章通过示例代码帮助读者更好地理解和应用单例模式。
39 0
|
4月前
|
设计模式 安全 Java
Java 编程中的设计模式:单例模式的深度解析
【9月更文挑战第22天】在Java的世界里,单例模式就像是一位老练的舞者,轻盈地穿梭在对象创建的舞台上。它确保了一个类仅有一个实例,并提供全局访问点。这不仅仅是代码优雅的体现,更是资源管理的高手。我们将一起探索单例模式的奥秘,从基础实现到高级应用,再到它与现代Java版本的舞蹈,让我们揭开单例模式的面纱,一探究竟。
49 11
|
3月前
|
设计模式 SQL 安全
【编程进阶知识】Java单例模式深度解析:饿汉式与懒汉式实现技巧
本文深入解析了Java单例模式中的饿汉式和懒汉式实现方法,包括它们的特点、实现代码和适用场景。通过静态常量、枚举类、静态代码块等方式实现饿汉式,通过非线程安全、同步方法、同步代码块、双重检查锁定和静态内部类等方式实现懒汉式。文章还对比了各种实现方式的优缺点,帮助读者在实际项目中做出更好的设计决策。
73 0
|
5月前
|
设计模式 存储 负载均衡
【五】设计模式~~~创建型模式~~~单例模式(Java)
文章详细介绍了单例模式(Singleton Pattern),这是一种确保一个类只有一个实例,并提供全局访问点的设计模式。文中通过Windows任务管理器的例子阐述了单例模式的动机,解释了如何通过私有构造函数、静态私有成员变量和公有静态方法实现单例模式。接着,通过负载均衡器的案例展示了单例模式的应用,并讨论了单例模式的优点、缺点以及适用场景。最后,文章还探讨了饿汉式和懒汉式单例的实现方式及其比较。
【五】设计模式~~~创建型模式~~~单例模式(Java)
|
5月前
|
设计模式 安全 Java
Java 单例模式,背后有着何种不为人知的秘密?开启探索之旅,寻找答案!
【8月更文挑战第30天】单例模式确保一个类只有一个实例并提供全局访问点,适用于需全局共享的宝贵资源如数据库连接池、日志记录器等。Java中有多种单例模式实现,包括饿汉式、懒汉式、同步方法和双重检查锁定。饿汉式在类加载时创建实例,懒汉式则在首次调用时创建,后者在多线程环境下需使用同步机制保证线程安全。单例模式有助于提高代码的可维护性和扩展性,应根据需求选择合适实现方式。
39 1