多线程进阶(一)
文章目录
- 多线程进阶(一)
- 单例模式
- 饿汉模式
- 懒汉模式
本篇主要引入多线程进阶的单例模式,为后面的大冰山做铺垫
代码案例介绍
单例模式
非常经典的设计模式
啥是设计模式
设计模式好比象棋中的 “棋谱”. 红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有一些固定的套路. 按照套路来走局势就不会吃亏.
软件开发中也有很多常见的 “问题场景”. 针对这些问题场景, 大佬们总结出了一些固定的套路. 按照这个套路来实现代码, 也不会吃亏.
单例 -> 单个实例(对象)
在有些场景下,希望有的类,只能有一个对象,不能有多个!在这样的场景下就可以使用单例模式了。
举个有意思的例子就是,你只能娶一个老婆,不过你可以生很多娃,娃就不一定是单例的
在代码中,有很多用于管理数据的对象,就应该是”单例“的,例如
mysql JDBC DataSource
(描述mysql
服务器的位置)
那么这里有个问题:
单例这个事情,还需要设计模式吗?
当我们写代码的时候,只给这个类
new
一次对象,不去new
多次不就行了?
实际上,设计模式是非常必要的,这其实上是属于设计模式,这完全就是取决于程序员的编程素养。
不过俗话说,宁可相信世界上有鬼,也不能相信人的这张破嘴!人是非常不靠谱的。
这样的思想方法在很多地方都会涉及到:
final
intreface
@Override
throws
于是此时就需要让编译器帮我们做监督,确保这个对象不会出现多个new
(出现多个的时候就直接编译报错)
饿汉模式
类加载的同时, 创建实例.
// 饿汉模式class Singleton{private static Singleton instance = new Singleton();//通过这个方法来获取到刚才的实例//后续如果想使用这个类的实例,都通过 getInstance 方法来获取public static Singleton getInstance(){return instance;}//把构造方法设置为私有,此时类外面的其他代码,就无法 new 出这个类的对象private Singleton(){}}public class Demo21 {public static void main(String[] args) {//此外又有一个实例,这就不是单例了//Singleton s1 = new Singleton();Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();System.out.println(s1 == s2);//true}}
懒汉模式
相比于饿汉模式,他就比较从容,在第一次使用的时候,再去创建实例
//懒汉模式class SingletonLazy{private static volatile SingletonLazy instance = null;public static SingletonLazy getInstance(){if (instance == null) {if (instance == null) {instance = new SingletonLazy();}}return instance;}private SingletonLazy(){}}public class Demo22 {public static void main(String[] args) {//此外又有一个实例,这就不是单例了//Singleton s1 = new Singleton();SingletonLazy s1 = SingletonLazy.getInstance();SingletonLazy s2 = SingletonLazy.getInstance();System.out.println(s1 == s2);//true}}
我们举个例子:
文本编辑器
记事本
需要打开一个非常大的文件
- 先把所有的内容都加载到内存中,然后再显示内容(加载过程会很慢)
- 只加载一小部分数据到内存,立即显示内容,随着用户翻页,再动态加载其他内容(懒汉模式)
那么这里我们抛出一个问题:
以上两种模式的写法,是否是线程安全的?(如果有多个线程,同时调用
getInstance
,是否会出问题?)
如果多个线程,同时修改同一个变量,此时就可能出现线程安全问题
如果多个线程,同时读取同一个变量,这个时候就没什么事情,不会有安全问题
那么我们看看饿汉模式的代码:
public static Singleton getInstance(){return instance;}
饿汉模式下的getInstance
只是进行读取,不是修改,那么他也就没有线程安全的问题了~
那我们看看懒汉模式的代码:
if(instance == null){ instance = new SingletonLazy();}return instance;
它既会读取,又会修改,就可能存在问题~
那么我们怎么保证懒汉模式是线程安全的呢?
类似于下面这种改法吗?
public static SingletonLazy getInstance(){if (instance == null) {synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}
当然不是,我们要明确一个事情,就是:锁,不是加了就安全
加的位置对不对,才是关键
- 锁的
{}
的范围,是合理的,能够把需要作为整体的每个部分都囊括进去 - 锁的对象,也得是能够起到合理的锁竞争的效果
这样才是真正的加锁
所以修改如下:
public static SingletonLazy getInstance(){synchronized (SingletonLazy.class) {if (instance == null) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}
把if
和new
合并成一个整体,此时的线程安全就迎刃而解了
不过一旦这么写的话,后续每次调用getInstance
,都需要先加锁了
不过这种操作属实是画蛇添足了,加锁是一个开销很大的操作,加锁就可能会涉及到锁冲突 一冲突就会引起阻塞等待。
还有就是:一旦某个代码涉及到 加锁,那么基本上就可以宣告这个代码和“高性能”无缘了~
但是实际上,懒汉模式,线程安全的问题,只是出现在最开始的时候(对象还没开始new呢)
一旦对象new出来了,后续多线程调用getInstance
,就只有读操作,就不会线程不安全了~
那么这里抛出一个问题:
是否有什么办法可以让代码线程安全,又不会对执行效率产生太多影响呢?
欲知后事如何,敬请期待下一篇博客~
主要是我临时有事,等明天写完再更