共计 4304 个字符,预计需要花费 11 分钟才能阅读完成。
写在后面
后面曾经向各位铁铁们介绍了 对于多线程的一些根本的知识点,为了能够让大家更好的了解 多线程的一些相干的个性,这篇博客 就会联合着一些具体的代码案例 来向大家介绍~
这篇博客就向大家介绍 对于多线程的第一个案例 —— 单例模式~
一、单例模式的概念
所谓单例模式,是一种常见的设计模式~
单例模式心愿:有些对象,在一个程序中应该只有惟一一个实例,就能够应用单例模式~
换句话说,在单例模式下,对象的实例化被限度了,只能创立一个,多了也创立不了~
如果关是靠人来保障,是不靠谱的,所以就借助语法,强行限度不能够创立多个实例,防止程序员不小心出错~
设计模式:相似于棋谱,是 前辈们曾经总结好了的一些固定套路,照着棋谱来下棋,棋就不会下的太差,这就进步了上限(比如说,象棋 ……)~
二、单例模式的简略实现
在 Java 外面的单例模式,有很多种实现形式,在这里次要介绍两个大类:饿汉模式、懒汉模式~
饿汉模式 和 懒汉模式 两种模式,形容了创立实例的工夫~
这两种模式,并没有什么高低贵贱之分,不是 现实生活中的 “ 贬义词 ”,相同 在计算机领域,” 懒 ” 还是一个褒义词,这个字意味着计算机的性能比拟高~
在计算机中,这种思维是很常见的~
比如说,想要理解某个材料(大文件 10G,寄存在硬盘中),那么 此时应用某个编辑器,关上文件,就会呈现两种状况:
- [饿汉] 把 10G 都读到内存中,读取结束之后 再容许用户进行查看和批改~
- [懒汉] 只读取一点点(以后屏幕能显示出的范畴),随着用户翻页,持续再读后续内容~
所以说,如果是 饿汉模式,那么显示所须要的工夫就会比拟多;如果是 懒汉模式,那么显示的工夫就会比拟低,效率就会比拟高(也有可能 用户关上文件当前,只看了两眼就关了,前面的大部分都没有读,那么内存读了那么多也没有意义)~
所以,通常咱们都认为,懒汉模式 要比 饿汉模式 更高效~
当然,还有 刷抖音、看小说、看微信、上网浏览内容 …… 的时候,都是借鉴了 懒汉模式~
2.1 饿汉模式
饿汉模式 的意思是,程序一旦启动,就会立即创立实例~
这就好比,一个饿了的人,看到一张饼,就会急不可待的往嘴里塞,咱们把它叫做 “ 饿汉 ”~
static 关键字 的前因后果:
static 名字叫做 “ 动态 ”,然而实际上和字面意思没有任何的关系,这是一个历史遗留的问题~
理论示意的含意是 “ 类属性 / 类办法 ”,同样的,咱们把 不是动态的一般的成员叫做 “ 实例属性 / 实例办法 ”~
Java 外面叫做 “ 动态 ” 是因为 C++ 外面示意 “ 类属性 ”,就是用 static,Java 是从 C++ 那里抄来的;而 C++ 则是因为 引入面向对象之后。须要搞一个形式来定义类属性,就须要引入一个关键字,然而引入新的关键字 老本极高,所以关键字设计者的大佬们 眼光就盯住了 旧的关键字~
于是,static 就中招了,static 原来示意的是 变量放到动态内存区,然而随着工夫的推移,零碎的进化,曾经没有 “ 动态内存区 ” 这个说法了,然而 static 关键字 还在,于是 “ 旧瓶装新酒 ”,就用来示意 “ 类属性 / 类办法 ” 了~
此时,” 类属性 / 类办法 ” 和 静不动态 字面意思上没有啥关系,只是 轻易找一个之前旧的关键字,当初没啥用了,赋予一个新的性能,仅此而已~
引入新的关键字老本极高的起因:
写代码的时候,变量名不能和关键字一样,当引入新的关键字的时候,不能够确定 其他人是不是曾经引入了 新的关键字 作为变量名(全世界的 C++ 代码那么多 ……)~
更大的可能是 新的关键字一引入,就会导致已有的一些代码 编译就会失败~
而后这把火就会烧到了 关键字设计者 的身上~
而类属性就长在类对象上,类对象在整个程序中只有惟一一个实例(JVM 保障的),所以说 类的动态成员就只有惟一一个了~
package thread;
// 单例模式,饿汉的形式
class Singleton {private static Singleton instance = new Singleton();
// 后续如果须要这个实例,就须要对立基于 getInstance 办法来获取 实力独苗,不要去 new 了~
public static Singleton getInstance() {return instance;}
// 构造方法设为 公有,此时 其余的类想来 new 就不能够了 (通过 编译器的规定来确保只有一个实例对象)~
private Singleton() {}
}
public class Demo19 {public static void main(String[] args) {
// 饿汉模式 的调用
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2);
}
}
运行后果:
如果不小心 想创立另一个实例,那么就会编译报错了:
总结:
应用 动态成员示意实例(唯一性)+ 让构造方法设为公有(堵住了 new 创立新实例的口子)~
依照下面的代码,当 Singleton 类 被加载的时候,就会执行到 实例化操作,此时 实例化的机会十分早(十分迫切的感觉),咱们把它称为 饿汉模式~
对于饿汉模式来说,在多线程的状况下,屡次调用的是 getInstance() 办法,而 这个办法只是一个 读操作,对于多线程读操作来说,是线程平安的~
2.2 懒汉模式
懒汉模式 的意思是,程序启动,先不焦急创立实例,等到真正用的时候,再创立实例~
这个也很形象,比拟 “ 懒 ”,不想干活,等到须要的时候再去干活~
// 懒汉模式的实现
class SingletLazy {
// 此处没有立刻创立实例
private static SingletLazy instance = null;
// 当首次调用 getInstance() 的时候,才会创立实例
public static SingletLazy getInstance() {if (instance == null) {instance = new SingletLazy();
}
return instance;
}
// 同理,创立构造方法 SingletLazy(),避免该类实例化其余的对象
private SingletLazy() {}
}
在多线程的状况下,懒汉模式,屡次调用 getInstance() 办法,而且波及到了 两次读操作(读出 instance 是否为空,读出 返回的 instance 值)和 一次写操作(批改 instance 变量的值),这是线程不平安的~
当然,一旦实例创立好了当前,后续 if 条件语句就进不去了,此时也就是 全是读操作了,也就线程平安了~
既然曾经明确了,懒汉模式 是线程不平安的,那么 如何解决懒汉模式线程不平安的问题呢?
方法就是 须要加锁!!!
通过 加锁 来保障 “ 判断 ” 和 “ 批改 ” 这组操作是原子的~
// 懒汉模式的实现
class SingletLazy {
// 此处没有立刻创立实例
private static SingletLazy instance = null;
// 当首次调用 getInstance() 的时候,才会创立实例
public static SingletLazy getInstance() {synchronized (SingletLazy.class) {if (instance == null) {instance = new SingletLazy();
}
}
return instance;
}
// 同理,创立构造方法 SingletLazy(),避免该类实例化其余的对象
private SingletLazy() {}
}
懒汉模式,只是在初始状况下,才会有线程不平安的问题,一旦实例创立好了当前,此时就平安了~
所以说,在后续调用 getInstance 的时候就不应该尝试加锁了~
如果应用上述的代码,无论 instance 是否为空(是否初始化),都会进行加锁,使得锁竞争加剧,耗费一些没有必要耗费的资源,就会很影响效率了~
在加锁之前,还须要进行判断 instance 是否为空(是否初始化),如果为空才会进行加锁:
// 懒汉模式的实现
class SingletLazy {
// 此处没有立刻创立实例
private static SingletLazy instance = null;
// 当首次调用 getInstance() 的时候,才会创立实例
public static SingletLazy getInstance() {if (instance == null) {synchronized (SingletLazy.class) {if (instance == null) {instance = new SingletLazy();
}
}
}
return instance;
}
// 同理,创立构造方法 SingletLazy(),避免该类实例化其余的对象
private SingletLazy() {}
}
剖析:
外层 if 断定以后是否曾经初始化好,如果未初始化好,就尝试加锁;如果曾经初始化好,那么就接着往下走~
里层 if 是在多个线程尝试初始化,产生了锁竞争,这些参加竞争的线程 拿到锁之后,再进一步确认,是否真的要初始化~
当然,下面的代码操作还是有一些问题的 —— 有的线程在读,有的线程在写~
这就联想起了 —— 内存可见性问题~
其实,这里的状况 和 之前的状况还不一样,每一个线程都有本人的上下文,都有本人的寄存器内容,按理来说 是不应该会呈现优化的~
然而,实际上也不好说,也并不能保障 编译器优化 是啥样的过程~
因而,给 instance 加上 volatile 是更加持重的做法~
如果不加 volatile 不肯定会有问题,然而 稳当起见,还是加上更好~
// 懒汉模式的实现
class SingletLazy {
// 此处没有立刻创立实例
volatile private static SingletLazy instance = null;
// 当首次调用 getInstance() 的时候,才会创立实例
public static SingletLazy getInstance() {if (instance == null) {synchronized (SingletLazy.class) {if (instance == null) {instance = new SingletLazy();
}
}
}
return instance;
}
// 同理,创立构造方法 SingletLazy(),避免该类实例化其余的对象
private SingletLazy() {}
}
总结:
懒汉模式 线程的三个要点:
- 加锁
- 双重 if
- volatile(不加可能是错的,但加了肯定是正确的)
对于 单例模式 的内容就先介绍到这里了,当然,在 Java 中也有许多其余的形式 也能够实现单例模式,如 基于枚举、基于外部类 等等~
如果感觉这一篇博客对你有帮忙的话,能够一键三连走一波,十分非常感谢啦 ~