前言
好久没写东西了,但是想着无论什么事还是要坚持自己初心要坚持的东西。写东西不能断!
对于常用的23种设计模式,这里笔者会根据自己学习和出现频率、重要程度进行学习记录吧。并且每种设计模式可能会根据暂时需求侧重学习深浅。
单例模式
有很多直接把单例分成很多种,这里我就分两个大类(饿汉懒汉)在这里说啦。
单例模式(Singleton Pattern)是设计模式中最简单的模式之一,属于创建型
模式。这种设计模式主要是类的对象只有一个实例,不需要每次new 创造。而我们要做的的就是确保这个对象创建的唯一
。然后根据一些特征进行优化创建以及访问改类。
而单例模式也有很多的应用,比如很多驱动例如摄像头、打印机等等,而在javaweb中的spring有很多配置文件,掌控全局,同样也是单例的。
对于单例,主要是全局只有这么一个对象。对了它的理解,这里笔者打几个有可能不太恰当的理解,目的在于帮助理解记忆,如果有错误还请指正。
个人可能不太恰当的理解:
- 一定程度上单例模式和普通的模式有可能是一根根火柴和打火机的差别。火柴想燃烧每次都要一根火柴摩擦燃烧为灰烬作为代价。而一个打火机可以持续供给。同样如果你只想在某个线程某个类想要得到执行改类的一个方法,如果这个类占用的内存、空间巨大,耗费的时间也很大的话,如果频繁创造是一笔很大的负担,那样就不如咱们就创建好一个然后供我们使用就好啦。
- 一定程度又像是他人和个人的区别。每个人都可能干过相同的事情,可能很多人都在校园的跑道上跑过步,但他们并不是你,你也可能在操场上跑过很多圈。但只有你知道你的思考。同样单例模式在一定程度可以看作一个人生,它可能有很多职能,也可能干过很多事、重复过很多事。但它可能对自己有从始至终的思考和记录。它可能有些全局参数记录着程序从始至终一些状态、信息等等。也就是它一直是它,你也一直是你独特的自己。
- 其他等等
至于单例模式的优缺点,这里就不作详细介绍了。无非是关于性能职能
、时间
、空间
、拓展性
等方面的一些讨论。这个可以参考不同人的不同理解。本文主要记录单例模式的实现方面。而同样单例模式实现上分为饿汉式和懒汉式:
单例模式创建要求:
- 某个类只能有一个实例(构造器私有化)
- 它必须自行创建这个实例(含有一个改类的静态变量来保存这个唯一的实例)
- 自行向整个系统提供这个实例(
直接暴露
或者用静态变量的get
方法)
饿汉式
直接创建对象,不存在线程安全问题 。对于饿汉式的实现是相对简单和容易的。在实际遇到这种类似的思想其实也很多。
理解:
- 这个饿汉式就好比双十一,你一下把你家里感觉可能缺的日后可能需要(也不一定需要的)全部给买了。比如啥洗衣液屯、棉衣棉鞋棉被日后可能要买也买。然后啥微波炉、烤箱我
感觉可能
以后会用我也买买买。买买买。这一下就买全了。但是:
- 同样在实际的生产,我们一个项目中可能有很多这样单例的存在,如果一次性启动的话对时间花销真的是太大。可能对程序的压力和体验都很差。所以一般很少直接使用。这种体验,像极了你打开某个网页等待JavaScript和css的过程。
饿汉式的几种实现方式:
01 . 直接实例化饿汉式(简洁直观)
/**
* 饿汉式
* 直接创建实例,不管是否需要这个对象
*/
public class singleton1 {
public static final singleton1 INSTANCE = new singleton1();
private singleton1()
{}
}
02 . 枚举式(最简洁)
public enum singleton2 {
INSTANCE
}
03 . 静态代码块饿汉式(适合复杂实例化)
public class singleton3 {
public static final singleton3 INSTANCE;
static {
/***
* 有可能一些数据需要配置,需要从类加载器加载
* 例如从某xxx.properties加载某些配置信息,我们只需更改配置文件不需要更改代码
*/
INSTANCE=new singleton3();
}
private singleton3()
{}
}
懒汉式
我们知道饿汉式在早早把对象创建好,在执行一些其他逻辑时候不存在线程安全的问题。但是懒汉式就不一样啦,延迟创建对象可能会有线程安全问题。
01 .线程不安全(适合单线程)
/***
* 懒汉式:构造器私有化
* 静态变量保存实例
* 提供一个静态方法,获取这个实例对象
*/
public class singleton4 {
private static singleton4 instance;
private singleton4(){}
public static singleton4 getInstance()
{
if (instance==null)
instance = new singleton4();
return instance;
}
}
对于这种单线程没问题,但是如果如果两个或多个线程来同时访问instance如果都为null同时申请的时候会遇到下图等之类问题,这违背了单例模式的设计原则并且可能会对系统数据造成错误。
02 .线程安全(适用于多线程)
怎么优化上述的懒汉式单例模式呢?既然不允许多个线程这样同时访问,那么咱们给它上个锁不久行了嘛!
public class singleton5 {
private static singleton5 instance;
private singleton5(){}
public static singleton5 getInstance()
{
synchronized (singleton5.class)
{
if (instance == null)
instance = new singleton5();
}
return instance;
}
}
但是这样有啥问题呢?就是我获取这个单例对象的时候,被上锁了。你气不气?我们就是在创建这个单例时候多线程可能出现点问题,咱么获取的时候不存在线程安全问题啊。。你为啥获取也要锁??
这就好比,咱们都喜欢看美女,跟美女说话要排队一个一个来,只能一个在一个时刻,但是你看美女不需要排队啊!!!
03 .线程安全推荐版(适用于多线程)
对于上述存在的问题,咱们只需要双重判定就行了。
- 首先判断instance是不是为null。如果不是null说明被实例化过直接返回即可!nice!
- 如果instance为null?上锁之后再判空实例化。
public class singleton6 {
private static singleton6 instance;
private singleton6(){}
public static singleton6 getInstance()
{
if(instance==null) {
synchronized (singleton6.class) {
if (instance == null)
instance = new singleton6();
}
}
return instance;
}
}
两个判断为null?为啥要两个?
- 第一个:用来判断是否为空需要上锁new 实例。
- 第二个:上锁后虽然只有一个会执行当前方法,但是很可能都为null的时候两个或多个都为null上锁的想构造。然后后面线程在等待同时前面线程构造好了,那么它就不需要再去构造这个
instance
啦!直接不操作就行了。
就这样,稍微完美的方法就这样产生了。
04 .静态内部类形式(适用于多线程)
上面的方法可能有些复杂,而静态内部类也是个好方式。主要是静态内部类和外部类不是一起加载的,并且你去调用它的时候他就会初始化,并且类加载是线程安全的,这个不需要考虑线程安全问题。当加载完之后你就可以直接拿啦。这样也能达到延迟加载的作用。
这个更详细你可以自己研究静态变量(类变量)、静态内部类等等加载顺序,研究下`static关键字。
public class singleton7 {
private singleton7(){}
private static class inner
{
private static final singleton7 instance=new singleton7();
}
public static singleton7 getInstance()
{
return inner.instance;
}
}
结语
学习参考尚学堂单例讲解以及百科、菜鸟教程等等,有些区别但是大部分实现都是相似的,带上个人理解分享给大家。如果有问题和疏漏还请指教!
欢迎交流学习、欢迎关注微信公众号:bigsai