设计模式--单例模式
来源:互联网 发布:蒙古语翻译软件 编辑:程序博客网 时间:2024/06/14 09:28
单例模式
确保一个类只有一个实例,并提供一个全局访问点。
在所有的设计模式中,单例模式是最简单也是最常用的一种设计模式,它只为一个实例提供一个全局对象,内次尝试去获取一个类的实例的时候,保证获取到的都是这一个对象。
这里我们给出它在Java当中的几种写法:
1.经典单例方法
public class Singleton { private static Singleton singleton; private Singleton() {} public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; }}
上面是第一种写法,当我们第一次调用getInstance()方法的时候,因为singleton是null的,所以就实例化一个。此后,每次再获取该实例的时候都不为null了,获取到的实例就是那个静态的singleton实例。
2.同步的单例方法
虽然上面的方法能达到获取单个实例的目的,但是在多线程中仍然是有问题的。如果一个线程正在尝试创建singleton的实例,而这时候刚好另一个线程进入了该方法。此时,因为第一个线程还没有将singleton创建完毕,singleton仍然是null,它也进入了该方法的内部。这时候执行到renturn的时候,可能返回两个实例。这在某些应用场景中可能会出现严重的错误,比如严格限制只能有一个类的实例的场景,或者当该类会占用很大的资源的时候。所以,为了解决这个问题,有必要对其在多线程环境中的使用进行一些优化。
下面是一种优化的策咯:
public class Singleton { private static Singleton singleton; private Singleton() {} public sychronized static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; }}
这样我们在该方法上添加了sychronized
关键字之后,就不会有两个方法同时进入getInstance()
方法内部了。
虽然上述方法能够达到我们保证只存在一个实例的目的,但是这对性能造成了一定的影响,而且是没有必要的影响。这是因为,我们其实只要在每次尝试去创建类的实例上面加锁就可以了,没必要在该方法上面加锁。因为,每次尝试去获取类的实例的时候,不是一定要创建的。只有第一次进入之该方法时会创建,随后的访问都是简单的获取操作了。而我们在该方法上面加锁之后,每次获取操作也都要等待锁。这显然是没必要的,因此我们还需要继续对其进行优化。
3.使用立即初始化的方式
如果程序在创建和运行时负担不繁重,我们可以采用立即初始化的方式来创建单例。
public class Singleton { private static Singleton singleton = new Singleton(); private Singleton() {} public static Singleton getInstance() { return singleton; }}
使用上面的做法,我们保证了JVM在每次加载该类的时候都会立即初始化唯一的实例,从而保证每次在获取的时候都只能获取到唯一的实例。虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法,但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。
与上面的方式类似的还有使用静态代码块来获取类的实例的方式:
public class Singleton { private static Singleton singleton; static { singleton = new Singleton(); } private Singleton() {} public static Singleton getInstance() { return singleton; }}
和上面在静态字段中直接初始化类的实例的方式差不都,只是将初始化的代码放进了一个静态代码块当中。这样实例的初始化时间仍然是在类第一次被加载的时候。
4.使用枚举
与上面使用静态立即初始化的方法类似,你也可以使用枚举的方式来获取一个类的实例。它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。下面是一个示例:
public enum Singleton { SINGLETON; Singleton() {}}
5.双重检查加锁的单例方法
利用双重检查加锁,首先检查是否实例已经创建了。如果实例尚未创建,才同步。这样只有在第一次创建的时候才会对多线程进行加锁限制:
public class Singleton { private volatile static Singleton singleton; private Singleton() {} public static Singleton getInstance() { if (singleton == null) { sychronized(Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }}
这里volitile关键字的作用是,当多线程实例化singleton之后会立即被其他线程看到。
6.使用静态内部类
public class Singleton { private static class SingletonHolder { private static Singleton INSTANCE = new Singleton(); } private Singleton() {} public static Singleton getInstance() { return SingletonHolder.INSTANCE; }}
这里定义了一个静态内部类SingletonHolder
,并在类中初始化一个静态Singleton
字段INSTANCE
。当调用了Singleton.getInstance()
来获取Singleton
实例的时候,会用SingletonHolder.INSTANCE
返回实例。
这种方式同样能够满足多线程的需求,它同样能够保证在第一次获取的时候将实例初始化,此后获取的都是实例化的结果。与上面使用静态字段不同的是,它没有把创建实例的静态字段或者静态代码块放在Singleton
中,而是放在了SingletonHolder
类中。在使用静态字段或者代码块的时候,只要Singleton
被加载,就会调用初始化Singleton
的操作。但是,在这方式中只有当使用SingletonHolder
类的时候才会执行初始化Singleton
的操作。也就是只有调用Singleton.getInstance()
方法的时候才会对Singleton
执行初始化。
想象一下,如果实例化Singleton
很消耗资源,我想让他延迟加载;另外一方面,我不希望在Singleton
类加载时就实例化,因为我不能确保Singleton
类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化Singleton
显然是不合适的。这个时候,这种方式相比使用静态字段或者代码块的方式就显得很合理。
如果将使用静态代码块和静态字段看作两个不同类型的单例方式,那么上面我们一共列出了7种不同的实现单例的方式。
关于单例模式中的一些小概念
1.饿汉式和懒汉式区别
- 饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,
- 而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。
另外从以下两点再区分以下这两种方式:
1.线程安全
- 饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,
- 懒汉式本身是非线程安全的,为了实现线程安全有几种写法,需要增加锁或者使用静态内部类的方式等。
2.资源加载和性能
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
其他
虽然,我们可以通过将一个类的构造器设置为private的,来避免外部直接调用构造器来初始化类的实例,但是用户可以通过AccessibleObject.setAccessible()方法,通过反射来调用私有构造器。我们可以让该类在创建第二个实例的时候抛出异常。
单例的序列化问题:仅仅在类上声明implements Serializable是不够的,还需要声明所有实例域都是瞬时(transient)的,并提供一个readResolve方法。否则,每次反序列化一个实例时,都会创建一个新的实例。当然,枚举单例不会有这个问题。
public static class Singleton implements Serializable { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton() {} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } private Object readResolve() { return SingletonHolder.INSTANCE; }}
原理是在ObjectInputStream内部获取实例之后,如果该实例实现了readResolve()方法,那么它就调用这个方法来获取。
更多内容
1、该项目整理了设计模式、Java语法、JVM、SQL、数据结构与算法等相关内容:https://github.com/Shouheng88/Java-Programming。
2、由于时间仓促,不免于存在错误,欢迎批评指正。
- 设计模式------单例模式
- 设计模式------单例模式
- 设计模式-单例模式
- 设计模式 - 单例模式
- 设计模式---单例模式
- 设计模式---单例模式
- 【设计模式】单例模式
- 设计模式-单例模式
- 设计模式----单例模式
- 设计模式--单例模式
- 设计模式-单例模式
- 设计模式-单例模式
- [设计模式] 单例模式
- 设计模式--单例模式
- 设计模式---单例模式
- 设计模式--单例模式
- 设计模式 -----单例模式
- 设计模式:单例模式
- Spring(三)注解装配
- 回到顶部按钮
- 第16章 Sun RPC
- idea中编码快捷键
- css3 居中定位方法
- 设计模式--单例模式
- MFC树形控件CTreeCtrl使用方法、遍历、SetCheck无效、根据名称获取节点、获取选中节点等问题
- ecshop中$user对象
- [Android]Error:Failed to open zip file.Gradle's dependency cache may be corrupt.
- CSS选择器及CSS继承性
- openxpki搭建
- 二分答案 Packmen CodeForces
- 当RecycleView遇到layout_width="0dp"
- Accessing Varibles outside a Lambda (C#)