创建型模式——单例模式

单例模式的定义与特点

单例模式(Singleton)的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。

单例模式有 3 个特点:

  • 单例类只有一个实例对象;
  • 该单例对象必须由单例类自行创建;
  • 单例类对外提供一个访问该单例的全局访问点。

单例模式的优点和缺点

单例模式的优点:

  • 单例模式可以保证内存里只有一个实例,减少了内存的开销。
  • 可以避免对资源的多重占用。
  • 单例模式设置全局访问点,可以优化和共享资源的访问。

单例模式的缺点:

  • 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
  • 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
  • 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。

单例模式的结构与实现

单例模式是设计模式中最简单的模式之一。通常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。

1. 单例模式的结构

单例模式的主要角色如下。

  • 单例类:包含一个实例且能自行创建这个实例的类。
  • 访问类:使用单例的类。

在这里插入图片描述
从上图可以得知:

  • 单例类私有化了构造函数,这是为了防止在外部进行 new 操作实例化;
  • 单例类含有一个公开的 getInstance() 方法用于获取单例类的实例;

2. 单例模式的实现

单例模式的实现方式有以下8种方式:

  1. 饿汉式(静态常量);
  2. 饿汉式(静态代码块);
  3. 懒汉式(线程不安全);
  4. 懒汉式(线程安全,同步方法);
  5. 懒汉式(线程安全,同步代码块);
  6. 双重检查;
  7. 静态内部类
  8. 枚举

2.1 饿汉式——静态常量

public class SingletonTest {

    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1 == singleton2);
    }
}

class Singleton {
    //1 声明一个当前类的静态常量,并初始化为类的实例
    private static final Singleton INSTANCE = new Singleton();
    //2 私有化构造函数,阻止在类的外部进行实例化
    private Singleton() {
        
    }
    //3 提供共有的获取实例的方法
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

优缺点说明

  • 优点:这种写法比较简单,就是在类加载的时候就完成了实例化,避免了线程同步的问题,在多线程环境中是安全的。
  • 缺点:在类加载的时候就完成了实例化,没有达到懒加载的效果,如果从未使用该实例,就会造成内存的浪费;
  • 这种方式在基于classloader机制避免了多线的同步问题,不过,instance在类装载时就实例化,在单例模式中大多数都是调用getInstance()方法,但是导致类装载的原因有很多,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到 Lazy Loading的效果;
  • 会造成内存的浪费

2.2 饿汉式——静态代码块

public class SingletonTest {

    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1 == singleton2);
    }

}

class Singleton {
    private static Singleton INSTANCE;
    private Singleton() {
    } 
    static {
        INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

优缺点说明:

  • 与静态常量的实现方式其实是一样的,静态代码块也是在类加载时执行的
  • 会造成内存浪费

2.3 懒汉式——非线程安全

public class SingletonTest {

    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1 == singleton2);
    }

}

class Singleton {
    //1 声明一个静态变量
    private static Singleton INSTANCE;
    //私有化构造方法,阻止在外部new
    private Singleton() {
    }
    // 在使用时才去实例化
    public static Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
}

优缺点说明:

  • 起到了懒加载的效果,但是只能在单线程环境中使用,多线程环境中会造成线程不安全;
  • 在开发中,不建议使用这种方式。

2.4 懒汉式——线程安全,同步方法

public class SingletonTest {

    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1 == singleton2);
    }
}

class Singleton {
    private static Singleton INSTANCE;
    private Singleton() {

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

优缺点说明:

  • 解决了线程安全问题
  • 效率太低,每一个线程在想要获取类的实例时,都需要同步执行getInstance()进行同步,如果线程数量很多,就会造成大量线程堆积在一起等待锁的释放,严重影响性能;

2.5 懒汉式——线程安全、同步代码块

我们发现上述同步方法的实现方式虽然可以解决线程安全问题,但是效率过低,并且在getInstance()方法中,我们只会对INSTANCE进行一次实例化操作,因此,我们可以将同步的操作降低到实例化语句上,这样就可以进一步优化同步的性能;

public class SingletonTest {

    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1 == singleton2);
    }

}

class Singleton {
    private static Singleton INSTANCE;

    private Singleton() {

    }

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

优缺点分析:

  • 这种方式本意是要解决同步方法性能过低的问题的,但是其实这种方式连线程安全都没解决,因为只要线程进入了if (INSTANCE == null) 语句里面以后,就无法保证线程安全问题了,因此这种方式其实是一种错误的实现方式;
  • 实际开发中,不能使用这种方式;

2.6 双重检查(DCI)

public class SingletonTest {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1 == singleton2);
    }
}

class Singleton {
    private static Singleton INSTANCE;
    private Singleton() {
    }

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

优缺点说明:

  • 即解决了线程安全问题,又达到了懒加载的问题,效率较高
  • 在实际开发中,推荐这种方式来实现;

2.7 静态内部类

静态内部类有两个特点:当外部类被装载的时候,静态内部类不会被立即装载;当通过外部类的方法调用静态内部类时,才会对静态内部类进行装载,而类在装载时是线程安全的;

public class SingletonTest {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1 == singleton2);
    }
}

class Singleton {
    //私有化构造器,阻止在外部new
    private Singleton() {
    }
    //定义一个静态内部类
    private static class SingletonHandler{
        //声明一个外部类的静态常量
        private static final Singleton INSTANCE = new Singleton();
    }
    //提供一个公共的获取实例的静态方法
    public static Singleton getInstance() {
        return SingletonHandler.INSTANCE;
    }
}

优缺点说明:

  • 这种方式采用了类装载的机制来保证实例化时只有一个线程,解决了线程安全问题;
  • 静态内部类方式在Singleton被装载时不会被装载,只有在调用getInstance()方法时,才会被类加载器装载;
  • 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类初始化时,别的线程是进不来的;
  • 即解决了线程安全问题,又达到了懒加载的目的
  • 实际开发中,推荐使用这种方式

2.8 枚举

public class SingletonTest {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.INSTANCE;
        Singleton singleton2 = Singleton.INSTANCE;
        System.out.println(singleton1 == singleton2);
    }
}

enum Singleton {
    INSTANCE;
}

优缺点说明:

  • 借助枚举不仅可以解决线程安全问题,还可以防止反序列化创建实例导致单例失效的问题;
  • 实际开发中,推荐使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值