这些可能大多数人都一知半解。今天就让我们大家一起来扒光单例模式的外衣,有深度的认识一下单例模式。
通过这篇文章你能学到什么
(建议你可以带着问题去学习)
-
单例模式的定义
-
单例模式在Android源码中的应用
-
单例模式的九种写法以及优劣对比
-
单例模式的使用场景
-
单例模式存在的缺点
-
接下来我们就一起进入今天的学习了
单例模式的定义
在学单例模式之前,我想大家都会自己问自己**:“单例模式存在的意义是什么?我们为什么要用单例模式?”**
众所周知,在古代封建社会,一个国家都只有一个国王或者叫皇帝。我们在这个国家的任何一个地方,只要提起国王,大家都知道他是谁。因为国王是唯一的。其实这个就是单例模式的核心思想:保证对象的唯一性。
单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。 单例模式是一种对象创建型模式。
从其定义我们可以看出来单例模式存在三个要点:
1、实例唯一性
2、自行创建
3、全局访问
如何设计一个优秀的单例模式其实也是围绕着这三点来的。
说了这么多了,还不知道单例模式到底啥样呢?接下来我们一起来着手设计这个“国王”的单例类。我们先看一下单例模式的类图:
单例模式的类图看起来很简单,一个私有的当前类型的成员变量,一个私有的构造方法,一个 getInstance 方法,**创建对象不再通过new 而通过 getInstance 让该类自行创建。**相信我们大多数人使用的单例模式都是这种,因为太简单了。但是单例模式的写法可不止这一种。接下来我们一起来看一下单例模式的九种写法。
单例模式的九种写法
一、饿汉式(静态常量)
/**
- 饿汉式(静态常量)
*/
class King {
private static final King kingInstance = new King();
static King getInstance() {
return kingInstance;
}
private King() {
}
}
-
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
-
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
二、饿汉式(静态代码块)
/**
- 饿汉式(静态代码块)
*/
class King {
private static King kingInstance;
static {
kingInstance = new King();
}
private King() {
}
public static King getKingInstance() {
return kingInstance;
}
}
-
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
-
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
三、懒汉式(线程不安全)
/**
- 懒汉式(线程不安全)
*/
public class King {
private static King kingInstance;
private King() {
}
public static King getKingInstance() {
if (kingInstance == null) {
kingInstance = new King();
}
return kingInstance;
}
}
优点:懒加载,只有使用的时候才会加载。
缺点:但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
四、懒汉式(线程安全)
/**
- 懒汉式(线程安全,同步方法)
*/
public class King {
private static King kingInstance;
private King() {
}
public static synchronized King getKingInstance() {
if (kingInstance == null) {
kingInstance = new King();
}
return kingInstance;
}
}
-
优点:懒加载,只有使用的时候才会加载,获取单例方法加了同步锁,保障线程安全。
-
缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。
五、懒汉式(线程安全,同步代码块)
/**
- 懒汉式(线程安全,同步代码块)
*/
public class King {
private static King kingInstance;
private King() {
}
public static King getKingInstance() {
if (kingInstance == null) {
synchronized (King.class) {
kingInstance = new King();
}
}
return kingInstance;
}
}
-
优点:改进了第四种效率低的问题。
-
缺点:不能完全保证单例,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
六、双重检查(DCL)
/**
- 双重检查(DCL)
*/
public class King {
private static volatile King kingInstance;
private King() {
}
public static King getKingInstance() {
if (kingInstance == null) {
synchronized (King.class) {
if (kingInstance == null){
kingInstance = new King();
}
}
}
return kingInstance;
}
}
-
优点:线程安全;延迟加载;效率较高。
-
缺点:JDK < 1.5 的时候不可用
-
不可用原因:由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化,可能会导致系统运行效率降低,而JDK 1.5 以及之后的版本都修复了这个问题。(面试装逼用,谨记!!!)
七、静态内部类
/**
- 静态内部类
*/
public class King {
private King() {
}
private static class KingInstance{
private static final King KINGINSTANCE = new King();
}
public static King getInstance(){
return KingInstance.KINGINSTANCE;
}
}
-
优点:避免了线程不安全,延迟加载,效率高。
-
缺点:暂无,最推荐使用。
-
特点:这种方式跟饿汉式方式采用的机制类似,但又有不同。
-
两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
八、枚举
/**
-
枚举
*/
public enum King {
KINGINSTANCE;
} -
优点:不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
尾声
你不踏出去一步,永远不知道自己潜力有多大,千万别被这个社会套在我们身上的枷锁给捆住了,30岁我不怕,35岁我一样不怕,去做自己想做的事,为自己拼一把吧!不试试怎么知道你不行呢?
改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
O-1714664532044)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!