【设计模式系列】(一) 单例模式

【设计模式系列】(一) 单例模式

一、单例模式简介

所谓类的单例模式、就是保证一个类中有且仅有一个实例、并且提供一个访问它的全局访问点。

单例模式不允许在其他类中取new它、只能通过一个(静态)方法来获取它的对象实例、我们知道如果某类没有定义构造器方法、那么会默认提供一个构造器方法、为了能够不被new、所以需要将构造器用private进行修饰、才能不被new

单例模式有如下8种实现

  • 饿汉式(静态常量)
  • 饿汉式(静态代码块)
  • 懒汉式(线程不安全)
  • 懒汉式(线程安全、同步方法)
  • 懒汉式(线程安全、同步代码块)
  • 双重检查
  • 静态内部类
  • 枚举
饿汉式(静态常量)

饿汉式就是类加载的时候进行实例化、

class Singleton{

    // 1、构造器私有化
    private Singleton(){

    }

    // 2、本类内部创建对象实例
    private final static Singleton instance = new Singleton();

    // 3、提供一个公有静态方法、返回实例对象
    public static Singleton getInstance(){
        return instance;
    }

}

优点:写法简单、在类装载的时候就完成实例化、避免线程同步问题。
缺点:类装载的时候就完成实例化、没有达到Lazy Loading的效果、如果从开始从未使用、则会造成内存浪费

饿汉式(静态代码块)
class Singleton{

    // 1、构造器私有化
    private Singleton(){

    }

    // 2、本类内部创建对象实例
    private static Singleton instance;

    static {
        instance = new Singleton();
    }

    // 3、提供一个公有静态方法、返回实例对象
    public static Singleton getInstance(){
        return instance;
    }
    
}

优点:写法简单、在类装载时就能完成实例化、不过不同的是实例化的过程放在静态代码块中、避免了线程同步问题、适用于单线程
缺点:在类装载时完成实例化操作、没有懒加载、会造成内存浪费

懒汉式(线程不安全)
// 懒汉式(线程不安全)
class Singleton{

    // 私有化构造器
    private Singleton(){

    }

    // 2、本类内部创建对象实例
    private static Singleton instance;

    // 3、提供一个公有静态方法、返回实例对象
    public static Singleton getInstance(){
        // 4、存在线程不安全、可能同时会有两个不同的线程进入到这个程序、导致程序出错
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }

}

在步骤4时会出现问题、当多个线程同时进入、会判断失效。
if(instance == null)

优点:实现了懒加载。(懒加载是在使用时才进行加载、不使用时就不加载、避免内存浪费、只有调用该方法时才进行加载)
缺点:在多线程的环境下、多个线程进入if(instance == null)、会判断失效、没有继续向下执行、第二个线程到来会继续进行创建实例、会导致创建多个实例。
结论:不推荐使用

懒汉式(线程安全、同步方法)

前面因为出现了线程安全的问题、所以为了解决这类问题、加🔒
但是🔒也不能乱加、在以下步骤修改:
1、提供一个静态的公有方法、在方法上添加synchrinized关键字、在方法内部进行实例化。

// 懒汉式(线程安全)、同步函数、
class Singleton{

    private Singleton(){

    }

    private static Singleton instance;

    // 实现了懒加载、解决线程安全问题
    // 效率太低、每个执行 getInstance()方法都要进行同步、效率低
    public static synchronized Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }

}

优点:解决了线程安全的问题
缺点:缺点也很明显、效率过低、执行getInstance()方法都要进行同步、其实只需要实例化一次就够了、后面想要获得该实例、直接return就足够了。
结论:不要使用。

懒汉式(线程安全、同步代码块)
// 懒汉式(线程安全)、同步代码块
class Singleton{

    private Singleton(){

    }
    // volatile防止指令重排序
    // 1、memory = allocate 分配对象的内存空间
    // 2、ctorInstance(memory) 初始化对象
    // 3、instance = memory 设置instance指向杠分配的内存地址
    private static volatile Singleton instance = null;
    // 1、实现了懒加载、并且解决了线程安全问题
    // 2、无法防止反射构造对象
    private static Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

}

使用volatile关键字、其阻止了变量访问前后的指令重排序、保证了指令的执行顺序
为了防止new Singleton被执行多次,因此在new操作之前加上Synchronized 同步锁,锁住整个类
两个instance==null的判断:对于instance存在的情况,就直接返回,这没有问题。当instance为null并且同时有两个线程调用GetInstance()方法时,它们将都可以通过第一重instance—null 的判断。然后由于lock机制,这两个线程则只有一个进入,另一个在外排队等候,必须要其中的一个进入并出来后,另一个才能进入。而此时如果没有了第二重的 instance是否为null 的判断,则第一个线程创建了实例,而第二个线程还是可以继续再创建新的实例,这就没有达到单例的目的。

优点:实现了懒加载、并解决了线程安全问题、效率极高
缺点:无法防止反射构造对象
结论:推荐使用

静态内部类

这种方式采用了类装载的机制来保证初始化时只有一个线程。
静态内部类中Singleton并不会随着类立即实例化、而是在需要实例化的时候进行实例化、调用方法Singletoninstance类、从而完成实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM 帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

// 静态内部类
class Singleton{

    private Singleton(){

    }

    private static class SingletonInstance{
        private static final Singleton INSTANCE = new Singleton();
    }

    public Singleton getInstance(){
        return SingletonInstance.INSTANCE;
    }

}

优点:避免线程不安全、利用静态内部类特点实现懒加载、效率高。
缺点:无法防止利用反射来重复构建对象。
结论:推荐使用

public class Demo{
	public static void main(String[] args) throws Exception{
		// 获取构造器
		Constructor con = Singleton.class.getDeclaredConstructor();
		// 设置可以访问
		con.setAccessible(true);
		// 构造不同对象
		Singleton singleton1 = (Singleton)con.newInstance();
		Singleton singleton2 = (Singleton)con.newInstance();
		// 验证是否是不同对象
		sout(singleton1  == singleton2 );
		
	}
}
枚举

枚举可以防止通过反射构建

public enum Singleton{
	INSTANCE;
	public void method(){
		sout("方法....");
	}
}

优点:枚举的实现单例模式、不但可以防止反射强行构建单例对象、而且可以在被反序列化的时候、保证反序列化的返回结果是同一对象。
缺点:没有实现懒加载、其单例对象在枚举类被加载时进行初始化
结论:推荐使用。

破坏单例模式案例

单线程情况下:

class Singleton{

    private static boolean flag = false;

    private Singleton(){
        if(flag){
            throw new RuntimeException("当前实例以创建");
        }
        flag = true;
    }

    private static class SingletonInstance{
        private static final Singleton INSTANCE = new Singleton();
    }

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

}

多线程时

class Singleton{

    private static boolean flag = false;

    private Singleton(){
        synchronized (Singleton.class){
            if(flag){
                throw new RuntimeException("当前实例以创建");
            }
            flag = true;
        }
    }

    private static class SingletonInstance{
        private static final Singleton INSTANCE = new Singleton();
    }

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

}

上述还有序列化创建多实例的情况没有总结。--------

总结注意事项
单例实现方式优点缺点结论
饿汉式(静态变量)避免同步问题、适用于单线程懒加载、造成内存浪费单线程
饿汉式(静态代码块)避免同步问题、适用于单线程懒加载、造成内存浪费单线程
懒汉式(线程不安全)避免内存浪费线程不安全×
饿汉式(线程安全、同步方法)实现了懒加载、解决线程安全效率低×
饿汉式(线程安全、同步代码块)提升效率效率低×
双重检查实现懒加载、解决线程安全、效率高无法防止反射来构建对象
静态内部类避免线程不安全、效率高、实现懒加载无法防止反射来构建对象
枚举避免线程同步问题、还能防止反序列化需要重新创建对象没有实现懒加载
注意事项
  • 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
  • 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new
  • 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值