目录
饿汉式 : 饿汉式类加载就会导致单例创建,如果实例用不到就会导致资源浪费。
设计模式
-
- 创建型模式
-
- 单例设计模式
-
- 饿汉式 :::类加载就会导致该单例对象被创建
-
- 恶汉式: 使用枚举实现
-
- 静态变量方式
-
- 静态代码块方式
-
- 懒汉式:::首次使用该单例对象才会被加载::
思维导图
单例模式的定义与特点
在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式。
单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。
单例模式有 3 个特点:
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点
单例模式的优点和缺点
单例模式的优点:
- 单例模式可以保证内存里只有一个实例,减少了内存的开销。
- 可以避免对资源的多重占用。
- 单例模式设置全局访问点,可以优化和共享资源的访问。
单例模式的缺点:
- 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
- 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
- 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
单例模式看起来非常简单,实现起来也非常简单。单例模式在面试中是一个高频面试题。希望大家能够认真学习,掌握单例模式,提升核心竞争力,给面试加分,顺利拿到 Offer。
小案例加深理解
单例设计模式分为:饿汉式 、 懒汉式
饿汉式的实现方式又分为 :静态代码块方式 和 静态变量方式
饿汉式 : 饿汉式类加载就会导致单例创建,如果实例用不到就会导致资源浪费。
静态变量方式
/** * 单例模式 * 懒汉式 * 静态变量方式 */ public class Singleton1 { private Singleton1(){} private static Singleton1 instance = new Singleton1(); public static Singleton1 getInstance(){ return instance; } }
静态代码块方式
/** * 单例模式 * 懒汉式 * 静态代码块 */ public class Singleton2 { private Singleton2(){} private static Singleton2 instance; static { instance= new Singleton2(); } public static Singleton2 getInstance(){ return instance; } }
单例模式下一个类的实例只允许存在一个
下面可以简单验证一下
public static void main(String[] args) { System.out.println("饿汉式 静态变量方式:"+(Singleton1.getInstance() == Singleton1.getInstance())); System.out.println("饿汉式 静态代码块方式:"+(Singleton2.getInstance() == Singleton2.getInstance())); }
都为true 说明获取的都是一个实例对象
懒汉式 : 第一次被访问才会去创建这个实例
/** * 单例模式 * 懒汉式 */ public class Singleton { private Singleton(){} private static Singleton instance; /*这里必须要加一个同步锁 如果是多线程 线程1进入if 然后线程2拿到了cpu执行权 线程1只能等 这样线程1和线程2都进入了if 会导致创建两个实例 * */ public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
以上代码还是有问题的,除了第一次赋值,之后都是读操作,而读操作是线程安全的不需要加锁,但是偏偏还加了锁,就会导致资源浪费。
所以可以使用双重检查模式,并且加上 volatile 关键字
package singleton.demo2; /** * 单例模式 * 懒汉式 */ public class Singleton { private Singleton(){} private static volatile Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
注意:如果编写的是多线程程序,则不要删除上例代码中的关键字 synchronized,否则将存在线程非安全的问题。如果不删除这关键字就能保证线程安全,但是每次访问时都要同步,会影响性能,且消耗更多的资源,这是懒汉式单例的缺点。
双重检查的方式显得比较繁琐 ,还能使用静态内部类实现懒加载。
JVM在加载外部类的时候不会加载静态内部类的,只有当静态内部类的属性或者方法被访问的时候才会被加载。
package singleton.demo2; public class Singleton2 { private Singleton2(){} private static class singleton{ private static final Singleton2 instance = new Singleton2(); } public static Singleton2 getInstance() { return singleton.instance; }
说明:
第一次加载singlcton类时不会去初始化INSTAMCE,只有第一次调用getInstanse,虚拟机加载SinglstonHlalda工并初始化INSTANCE,这样不仅能确保线程安全,也能保证singleton类的唯一性。
小结:
静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
枚举方式 在不考虑内存消耗下使用
public enum Singleton3 { INSTANCE; }public static void main(String[] args) { System.out.println(Singleton3.INSTANCE == Singleton3.INSTANCE); }
枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。