多线程代码案例

单例模式(设计模式的一种)

顾名思义, 就是单个实例. 在一个Java进程中, 要求指定的类, 只能有唯一一个实例. 通过特殊技巧, 来保证这里的实例不会有多个. (尝试 new 多个实例的时候, 就会编译报错)

1. 饿汉模式

代码演示:

这样写, 这个类被加载的时候就会初始化已经创建好的静态成员变量, 由于创建时机非常早, 所以叫饿汉. 并且把构造方法设为私有, 那么后续再想 new 这个 Singleton 就会直接编译报错.

2. 懒汉模式

创建实例的时机就不一样了, 只有在第一次使用的时候, 才会创建实例.

这个引用指向唯一实例, 先不着急创建, 把它初始化为 null , 如果是第一次调用 getInstance(), 才会创建实例, 后面再调用, instance 已经不为空了, 也就不会再创建实例了.

这样设定, 仍然能保证该类的实例是唯一一个, 与此同时, 创建实例的时机就不是程序驱动时了, 而是第一次调用 getInstance() 的时候, 这个操作的执行时机就不知道了, 甚至整个程序压根用不到这个方法, 也就把创建的操作省下了.

3. 关于线程安全

上面介绍的只是序幕~ 接下来讨论的就是关键问题:

上面编写的代码 (懒汉模式 和 饿汉模式) 是否是线程安全的??

如果在多线程下, 并发调用 getInstance() , 这两个代码是否是线程安全的呢??

1. 加锁

首先, 对于 饿汉模式 来说, 多个线程读同一个变量, 这是安全的. 

而对于 懒汉模式 来说, 多个线程同时运行, 有可能会创建出多个实例, 线程不安全.

要想保证线程安全, 就需要把 if 和 new 打包成一个原子操作. 也就是 加锁 !

2. 省去无意义的加锁

代码到这一步, 还有点小问题~~

如果 instance 已经创建过了, 后面再调用 getInstance() 这个方法, 就只剩读操作了, 对于读操作还要加锁, 此时效率就会降低, 显然这是没必要的~

加锁就意味着产生阻塞, 一旦产生阻塞, 什么时候解锁, 这就不确定了, 所以一旦加锁, 就注定与 "高性能" 无缘了.

所以我们在外面再套一层 if.

可以看到 这两个 if 判断条件是一样的, 对于 单线程/非阻塞 来说, 这是无意义的, 但对于 多线程/可能阻塞的代码里, 这就很有必要了!! 这样的代码称为 "双重校验锁".

3. 指令重排序

那么到这一步, 还有最后一个问题 !!

是由于 指令重排序 引起的线程安全问题.

指令重排序也是编译器优化的一种方式. 调整原有代码的执行顺序, 在原有逻辑不变的情况下, 提高程序的效率.

instance = new SingletonLazy();

这一句代码, 做了三件事:

  1. 向内存申请一块空间
  2. 调用构造方法, 创造出这个实例
  3. 把这块内存地址赋值给 instance.

在多线程下, 指令重排序就可能引入问题了!

总结:

t1 有可能执行完 1 3 就被调度走了, 还没来得及创建实例, 这时 instance 指向的是 未被初始化 的对象, t2 执行判断的时候, instance 已经不为空了, 没有进入 if 触发加锁, 也就没有阻塞, 直接 return 了!! 如果 t2 继续使用 instance 里的属性和方法, 就会出现错误(因为里面的属性都是 未初始化的 "全0" 的值), 就可能会引起代码的逻辑出现问题.

4. volatile 关键字

解决方法: 使用 volatile 关键字!

volatile 有两个功能:

  1. 保持内存可见性. 每次读取变量必须重新访问内存, 不会优化到 寄存器/缓存 中.
  2. 禁止指令重排序. 针对被 volatile 修饰的变量相关的读写操作相关的指令, 是不能被重新排序的.

改进之后 :

所以, 关于 懒汉模式 完整的代码 :

class SingletonLazy {
    private volatile static SingletonLazy instance = null;
    public static SingletonLazy getInstance() {
        //如果 instance 为空, 说明是首次调用, 就需要考虑线程安全问题, 要加锁.
        //如果 instance 不为空, 就直接返回 instance.
        //判断是否加锁
        if(instance == null) {
            synchronized (SingletonLazy.class) {
                //判断是否要创建对象
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy() { }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值