
本篇文章要讲解的是关于单例模式的一些问题。无论是在面试或是在工作中我们都会使用到单例这种设计模式,实现单例的方式有很多,它们之间有什么区别和优劣呢?接下来就让本公子来一一替你解答吧。
什么是单例
单例模式指的是在整个系统 / 应用中只有 一个 实例对象。
单例有什么好处
如在系统中去请求网络时的网络对象往往只需要一个,可以反复利用,无需每次都新建,从而降低内存,提高性能等等。
单例的写法
在 Java 中存在着多种单例的写法,以下挑出几种常见的写法来介绍一下。在多种单例的写法中大都存在着以下几个特点:
- 私有化构造方法
- 提供一个公开静态的方法来获取单例对象实例
- 注意在多线程的情况下是否线程安全
- 注意在反序列化时是否会创建新的对象实例
接下来就来介绍一下单例的几种写法:
懒汉式
这是最简单的单例写法,先私有化构造方法,定义静态单例对象,提供公开静态获取单例对象的方法,在里面判断,如果对象为空时就新建一个对象。但这种写法是线程不安全的,在多线程情况下,如果一个线程在新建时另一个线程判断此时的单例为空,又去创建一个新的对象实例,这样就会造成创建多个对象实例的情况,因此是线程不安全的。
/**
* 懒汉式-线程不安全
*/
static class Singlon_1 {
//私有构造函数
private Singlon_1() {
}
private static Singlon_1 mInstance_01;
public static Singlon_1 getInstance() {
if (mInstance_01 == null) {
mInstance_01 = new Singlon_1();
}
return mInstance_01;
}
}
懒汉式(线程安全版)
上面说到懒汉式的写法是不安全的,因此在这个公开的静态方法加上 synchronized (加锁)关键字,从而保证线程安全,但是这种方式的加锁粒度太大,会耗费性能:
/**
* 懒汉式-线程安全-在方法体上+synchronized
* 粒度太大-蚝性能
*/
static class Singlon_2 {
//私有构造函数
private Singlon_2() {
}
private static Singlon_2 mInstance_02;
public synchronized static Singlon_2 getInstance() {
if (mInstance_02 == null) {
mInstance_02 = new Singlon_2();
}
return mInstance_02;
}
}
懒汉式进阶->DCL 双加锁(线程安全)
这种方式降低锁的粒度,从而提高性能,同时由于 JDK 中指令执行顺序的不确定性,因此需要使用 volatile 关键字,这是从 JDK 1.5引入的一个关键字,主要有两个作用:
- 防止指令重排
- 每次获取数据强制从 "主存" 中拿,然后再存取到 "主存" 中
同时前面说过还需要注意在反序列化时是否会创建新的单例对象,因此还要重写 readResolve 方法,返回自己定义的这个单例对象,防止在反序列化的时候创建新的单例对象:
/**
* DCL-双加锁
* 懒汉式-线程安全
*/
static class Singlon_3 {
//私有构造函数
private Singlon_3() {
}
//防止指令重排
//每次获取数据从主存去拿
private volatile static Singlon_3 mInstance_03;
public static Singlon_3 getInstance() {
if (mInstance_03 == null) {
synchronized (Singlon_3.class) {
if (mInstance_03 == null) {
mInstance_03 = new Singlon_3();
//1.给构造函数分配地址
//2.实例化单例对象
//3.将构造函数赋值给单例对象
}
}
}
return mInstance_03;
}
//重写此方法,防止反序列化时新建对象
private Object readResolve() throws ObjectStreamException{
return mInstance_03;
}
}
饿汉式(线程安全)
这种方式在创建实例的时候就直接赋值了单例对象,这种方式也是线程安全的:
/**
* 饿汉式-线程安全
*/
static class Singlon_4 {
//私有构造函数
private Singlon_4() {
}
//一开始就实例化对象
private static Singlon_4 mInstance_04 = new Singlon_4();
public static Singlon_4 getInstance() {
return mInstance_04;
}
}
静态内部类式(线程安全)
使用这种方式创建单例对象,有两个好处:
- 可以做到需要使用时才加载,有效提高性能(有点懒加载的意思)
- 同时保证了内存中只存在一份该对象实例
/**
* 静态内部类-线程安全
*/
static class Singlon_5 {
//私有构造函数
private Singlon_5() {
}
private static class Holder {
static Singlon_5 mInstance = new Singlon_5();
}
public static Singlon_5 getInstance() {
//延迟加载-需要用到时才加载
//同时保证内存中只存在一个对象实例
return Holder.mInstance;
}
}
容器式
我们去看 Java 中的源码时,也可以看到有使用容器的方式去创建单例的方式:
/**
* 使用容器
*/
static class Singlon_6 {
private static Map<String, Object> objMap = new HashMap<>();
public static void registInstance(String key,Object instance){
if (!objMap.containsKey(key)){
objMap.put(key,instance);
}
}
public static Object getInstance(String key){
return objMap.get(key);
}
}
枚举式(线程安全)
还可以使用枚举的方式去创建一个单例,在 Java 中使用枚举创建的单例对象默认是线程安全的:
/**
* 枚举-默认线程安全
*/
enum Singlon_7{
SINGLON_7
}
以上就是 Java 中单例的几种写法,在 Kotlin 中应该如何创建单例呢?
我们对照Java中静态内部类式改造成 Kotlin 的写法吧:
// 只有一个实例
class NetManager {
object Holder {
val instance = NetManager()
}
// 看不到 static 可以 派生操作
companion object {
// 全部都是 相当于 Java static
fun getInstance() : NetManager = Holder.instance
}
}
其实在 Kotlin 中给我们提供了 object 关键字,只需使用了这个关键字,在里面写的内容就是一个单例,上面只是一个仿照Java静态内部类改造的写法而已,Kotlin 官方推荐我们使用的仍旧是使用 object 关键字来创建单例。
以上就是我对单例模式的一些小解析,希望对你有所帮助~
网友评论