java设计模式--你真的学会单例模式了嘛?

前言

现在是2020年5月12日22点51分,也是我重新开始学习和整理java设计模式的开始。希望我自己能坚持下来完成这23个设计模式感觉立了一个大flag ,但一篇文章就是一个进步对吧?

最近看到鲁迅写给当时年轻人加油的一句话,我也觉得在某些时候激励了我自己,送个大家一起共勉。
前途很远,也很暗,然而不要怕,不怕的人的面前才有路。

一、什么是单例模式

如果你已经了解过单例模式,那么不妨直接阅读第四大点。直接学习一下枚举来实现单例模式,你就能了解到为什么Joshua Bloch大神都推崇这样来实现单例模式

1.1 基本概念

单例这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

设计模式被大致分为了三种:创建型模式、结构型模式、行为型模式

人话:在java中使用工具类的实现或创建对象需要消耗资源,那么它只允许被创建一次。所有代码都使用这一个实例,那么它就能节约出一部分的内存

1.2单例模式的特点

1、单例类只能有一个实例,节省内存空间
2、单例类必须自己创建自己的唯一实例
3、单例类必须给所有其他对象提供这一实例
4、设计模式中最为简单的几个设计模式
5、自己来控制这个实例,符合面向对象的封装原则

1.3 使用单例的注意事项

1.利用反射的方式来实例化多个不同的实例(可以使用枚举解决)
2.在序列化和反序列换的时候也会出现多个不同的实例(可以使用枚举解决)
3.不要做断开单例类对象与类中静态引用的危险操作。
4.多线程使用单例使用共享资源时,注意线程安全问题

二、为什么需要单例模式

当一个全局使用的类频繁地创建与销毁,会消耗我们一部分系统资源来处理这个过程。那么我们就只保证一个类Class只有一个实例存在, 就可以帮助节省内存,因为它限制了实例的个数,也有利于Java垃圾回收(garbage collection)。
使用场景
1、要求生产唯一序列号。
2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

三、单例模式的常见写法

3.1 立即加载与延迟加载

在介绍单例模式实现之前,我们先解释一下 立即加载 和延迟加载 两个概念。

  • 立即加载 : 在类加载初始化的时候就主动创建实例;

  • 延迟加载 : 等到真正使用的时候才去创建实例,不用时不去主动创建。(内存容量有限 ,为了减少并发量,在真正需要数据的时候才进行数据加载操作,减少系统资源的消耗)

延迟加载也被使用在Hibernate3关联关系对象中默认的加载方式,在我们进行一对多查询的时候,我们设置一个lazy=true/false ,数据库就可以根据这个值来决定是否加载附属的信息。

常用实现方式:饿汉式、懒汉式、静态内部类、双重校验锁,最后会让以上几种与枚举来进行比较,从而系统的了解各种方式方式的优劣

3.1饿汉式单例

特点
1.天生就是线程安全,较为常用的一种模式,
2.会提前加载资源

public class Singleton {  
	//指向自己实例的私有静态引用,主动创建
    private static Singleton instance = new Singleton();  
    //私有构建方法
    private Singleton (){}  
    //给所有其他对象提供的自己对象的实例
    public static Singleton getInstance() {  
    return instance;  
    }  
}

3.2懒汉式单例

懒汉式天生是非线程安全的,需要我们使用同步进行保证线程安全。
手动实现两种方式:同步代码块与同步方法,而同步代码块的作用区间更小。所以同步代码块使用的次数更多

同步方法块--实现方式的运行效率会很低,因为同步块的作用域有点大,而且锁的粒度有点粗
public class Singleton {  
    private static Singleton instance;  
     //私有构建方法
    private Singleton (){}  
    //给所有其他对象提供的自己对象的实例,使用了synchronized进行加锁
    public static synchronized Singleton getInstance() {  
    //调用该方法才进行创建
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}

同步代码块--作用区间更小
public class Singleton2 {
 
    private static Singleton2 singleton2;
 
    private Singleton2(){}
 
 
    public static Singleton2 getSingleton2(){
        synchronized(Singleton2.class){  // 使用 synchronized 块,临界资源的同步互斥访问
            if (singleton2 == null) { 
                singleton2 = new Singleton2();
            }
        }
        return singleton2;
    }
}

3.3静态内部类单例

特点
1.与懒汉式加载类似,也是使用时进行加载实例。
2.不会出现线程安全问题,但效率比懒加载更高

public class Singleton {  
	//私有内部类,也是使用时在进行加载。线程安全
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
}

3.4双重校验锁单例(最常用)

特点
1.除去枚举,单例模式最好的实现方式。
2.同时保证了线程安全与程序的运行效率
3.字段必须使用volatile进行修饰,避免singleton=new Singleton()对象的创建在JVM中可能会进行重排序

当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

public class Singleton {  
	//必须使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个不完整的实例
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    //第一重:判断是否有实例对象,没有就使用同步操作进行创建实例对象,创建成功可以避免同步锁从而加快运行效率
    if (singleton == null) {//使用同步代码块
        synchronized (Singleton.class) {  
        //第二重是为了防止在刚使用这个类的时候(还没有创建好实例对象),有多个对象已经进入了第一重校验,为了保证单例(只创建一个对象)所以还需要一重校验
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}

3.5 小结

其实通过上面代码的实现优化,我们就发现了我们是基于两点来进行实现单例模式的:
保证线程安全的同时,减少同步的区域与次数

四、枚举实现单例模式(最佳方式)

4.1为什么不使用常规的模式实现单利

常用方式:饿汉式、懒汉式、静态内部类、双重校验锁
以上方式都存在缺陷:
1.利用反射的方式来实例化多个不同的实例
2.在序列化和反序列换的时候也会出现多个不同的实例

4.2使用枚举实现单利的原因

单利模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点
枚举:
代码简单
枚举默认就是单利且线程安全
枚举单例可以自己处理序列化

4.3使用枚举实现单利的好处:

Joshua Bloch大神《Effective Java》中有提到,因为其
1.功能完整、使用简洁、无偿地提供了序列化机制、在面对复杂的序列化或者反射攻击时仍然可以绝对防止多次实例化等优点,
2.单元素的枚举类型被作者认为是实现Singleton的最佳方法。

4.4实现

//使用枚举实现SingleEnum类的单利
public enum SingleEnum(){
	//枚举的实例化对象,枚举中每个实例化对象都等价与static final修饰
	//表明也只能被实例化一次
	INSTANCE;
	//枚举中构造方法默认---私有化,可以默认不写
	//保证调用枚举(SingleEnum)的实例对象(INSTANCE)才会调用
	SingleEnum(){
	system.out.println("我只会被调用一次");
	}
	//public void xxx方法()
	//public void xxx方法()
}
//获取Single的实例对象
SingleEnum.INSTANCE;
//运行结果
我只会被调用一次

上面的Singleton不会被反射获取多个实例,不用考虑序列化问题.其实更加简化

public enum SingleEnum(){
	INSTANCE;
}
//获取Single的实例对象
SingleEnum.INSTANCE;

五、单例面试题讲解

1.请思考在一个jvm中会出现多个单例吗?

在分布式系统、多个类加载器、以及序列化的的情况下,会产生多个单例,这一点是无庸置疑的。那么在同一个jvm中,会不会产生单例呢?使用单例提供的getInstance()方法只能得到同一个单例,除非是使用反射方式,将会得到新的单例d

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1) 优秀的程序应该是这样的:阅读时,感觉很优雅;新增功能时,感觉很轻松;运行时,感觉很快速,这就需要设计模式支撑。2) 设计模式包含了大量的编程思想,讲授和真正掌握并不容易,网上的设计模式课程不少,大多讲解的比较晦涩,没有真实的应用场景和框架源码支撑,学习后,只知其形,不知其神。就会造成这样结果: 知道各种设计模式,但是不知道怎么使用到真实项目。本课程针对上述问题,有针对性的进行了升级 (1) 授课方式采用 图解+框架源码分析的方式,让课程生动有趣好理解 (2) 系统全面的讲解了设计模式,包括 设计模式七大原则、UML类图-类的六大关系、23种设计模式及其分类,比如 单例模式的8种实现方式、工厂模式的3种实现方式、适配器模式的3种实现、代理模式的3种方式、深拷贝等3) 如果你想写出规范、漂亮的程序,就花时间来学习下设计模式吧课程内容和目标本课程是使用Java来讲解设计模式,考虑到设计模式比较抽象,授课采用 图解+框架源码分析的方式1) 内容包括: 设计模式七大原则(单一职责、接口隔离、依赖倒转、里氏替换、开闭原则、迪米特法则、合成复用)、UML类图(类的依赖、泛化和实现、类的关联、聚合和组合) 23种设计模式包括:创建型模式:单例模式(8种实现)、抽象工厂模式、原型模式、建造者模式、工厂模式。结构型模式:适配器模式(3种实现)、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式(3种实现)。行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)2) 学习目标:通过学习,学员能掌握主流设计模式,规范编程风格,提高优化程序结构和效率的能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值