Codull 的逆袭之路——你还不会单例模式吗?
聊聊单例模式
大佬的介绍
anthony1314
前言
介绍
-
意图: 保证一个类仅有一个实例,并提供一个访问它的全局访问点 -
主要解决: 减少一个全局使用的类频繁地创建与销毁所带来的消耗 -
特点: -
类构造器私有 -
持有自己类型的属性 -
对外提供获取实例的静态方法 -
使用场景: -
需要频繁实例化然后销毁的对象 -
创建对象时耗时过多或者耗资源过多,但又经常用到的对象 -
有状态的工具类对象 -
频繁访问数据库或文件的对象,如 I/O 与数据库的连接等
实现
懒汉式(一)
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
synchronized
概念
-
互斥性 :即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问。互斥性我们也往往称为操作的原子性。 -
可见性 :必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。
锁的分类
-
对象锁: 在 Java 中,每个对象都会有一个 monitor 对象,这个对象其实就是 Java 对象的锁,通常会被称为“内置锁”或“对象锁”。类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰。 -
类锁: 在 Java 中,针对每个类也有一个锁,可以称为“类锁”,类锁实际上是通过对象锁实现的,即类的 Class 对象锁。每个类只有一个 Class 对象,所以每个类只有一个类锁。
用法
-
修饰代码块 -
synchronized(this|object) {} -
synchronized(类.class) {} -
修饰方法 当修饰静态方法的时候,线程获取到的则是类锁,反之获取到的对象锁。 -
修饰非静态方法 -
修饰静态方法
关于其他
懒汉式(二)
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
双重校验锁(DCL)
public class DoubleLock {
public static volatile DoubleLock doubleLock = null;
private DoubleLock(){}
public static DoubleLock getInstance(){
if(doubleLock == null){
synchronized (DoubleLock.class){
if(doubleLock == null){
doubleLock = new DoubleLock();
}
}
}
return doubleLock;
}
}
-
第一次判断,假设会有好多线程,如果 doubleLock 没有被实例化,那么就会到下一步获取锁,只有一个能获取到,如果已经实例化,那么直接返回了,减少除了初始化时之外的所有锁获取等待过程 -
第二次判断是因为假设有两个线程 A、B,两个同时通过了第一个 if,然后 A 获取了锁,进入然后判断 doubleLock 是 null,他就实例化了 doubleLock,然后他出了锁,这时候线程 B 经过等待 A 释放的锁,B 获取锁了,如果没有第二个判断,那么他还是会去 new DoubleLock(),再创建一个实例,所以为了防止这种情况,需要第二次判断 -
明白了两次判断的作用后,细心的同学就发现了我们在 DoubleLock 声明时,在类前面加了这么一个关键字volatile,这里对于这个关键字涉及到了两个概念指令重排序,内存可见。 对于 下面这一句代码 其实分为三步: doubleLock = new DoubleLock();
-
开辟内存分配给这个对象 -
初始化对象 -
将内存地址赋给虚拟机栈内存中的 doubleLock 变量
volatile
JMM(JavaMemoryModel)
JMM
:Java 内存模型,是 java 虚拟机规范中所定义的一种内存模型,Java 内存模型是标准化的,屏蔽掉了底层不同计算机的区别(
注意这个跟JVM完全不是一个东西 ,总是有同学搞错,虽然笔者初学也经常把这两者搞混
)。
JMM 规定
线程对变量的所有的操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量
。
可见性
禁止指令重排序
静态内部类
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
枚举方法
public enum Singleton {
INSTANCE;
public void otherMethods() {
}
}
总结
小广告
如果有与代码或者计算机有关的知识想要了解的话!
我们会第一时间为你准备你的专属攻略!