多线程的案例1--单例模式

设计模式

设计模式(Design Patterns),是指在软件设计中,被反复使用的一种代码设计经验

所谓的设计模式其实就是计算机前辈们根据一些常见的需求场景, 整理出来的一些对应的解决办法, 类似于棋谱, 只要照着这些设计模式写,不至于很好,但是也不至于写的很差

设计模式有很多, 本文主要是关于单例模式 和 工厂模式

单例模式

单例模式 : 只能创建一个实例(instance)的设计模式, 只能创建一个实例这是由需求决定的

之前学的类与对象,一个类可以创建多个对象, 举个例子: 假设车是一个类, 那么小轿车可以是一个对象, 火车可以是一个对象…

之前学习的JDBC编程创建的DataSource类也是只会创建出一个实例

单例模式,本质上就是借助编程语言自身的语法特性, 强行限制某一个类,只能创建一个实例

在Java中, 存在一个天然的只能有一个实例的东西,那就是static

static 成员/属性 就变成了类成员/类属性,此时就只有一份了

更加具体地说, 类创建的对象就是类对象 , 类对象是由JVM加载.class文件来的, 而JVM加载.class文件只会加载一次, 也就是会有一个类对象, 类对象里面的static修饰的成员/属性也就只有一份了

饿汉模式

image-20220917142326774

要是在main中有创建出一个实例,就不是单例模式了, 所以要想办法禁止在类外面创建新的实例

package Threading;
class Singleton{
    private static Singleton instance = new Singleton();//进行实例化,此时的实例是私有的&&是属于类的,只有一份

    public static Singleton getInstance() {
        return instance;
    }
    private  Singleton(){  //私有的构造方法

    }
}
public class Demo20 {
    public static void main(String[] args) {
        //要想禁止在内外面创建新的实例,只要将类的构造方法设为私有的就行了
         Singleton instance = Singleton.getInstance();//这样子才能得到instance
    }
}

以上的代码 在Singleton类中在类加载阶段就创建了实例 , 这就是单例模式中的饿汉模式

懒汉模式

在单例模式中还有一个方式—懒汉模式

package Threading;
class SingletonLazy{
    private static SingletonLazy instance = null;//先不实例化
    public static SingletonLazy  getInstance   () {
        if(instance == null){  //首次调用getSingleLazy时,才会实例化
            instance = new SingletonLazy();
        }
        return instance;
    }
    private  SingletonLazy(){  //私有的构造方法禁止类外创建出新的实例
        
    }
}
public class Demo21 {
    public static void main(String[] args) {

    }
}

要是没有调用过getSingleLazy 就不会创建出实例, 或者就算是调用了getSingleLazy,在程序刚刚开始启动的时候, 系统资源比较紧张,创建实例的时机靠后,也能提高效率

image-20220917152520498

image-20220917152528558

以上两种代码哪一种是线程安全的?哪一种是线程不安全的?

所谓的线程安不安全就是说调用getInstance的时候, 会不会有问题

饿汉 只涉及到了读取操作

懒汉 既有读取操作,又有修改操作, 要是多个线程同时修改同一个变量就会出现线程不安全

要想解决懒汉模式线程不安全的情况,就要加锁

package Threading;
class SingletonLazy{
    private static SingletonLazy instance = null;//先不实例化
    public static SingletonLazy  getInstance  () {
        synchronized (SingletonLazy.class) { //锁对象是类对象
            if (instance == null) {  //首次调用getSingleLazy时,才会实例化
                instance = new SingletonLazy();
            }
            return instance;
        }
    }
    private  SingletonLazy(){

    }
}
public class Demo21 {
    public static void main(String[] args) {

    }
}

加上锁就能够保证线程安全,但是这样写的话, 每次都要加锁, 加锁的开销是比较大的(加锁会涉及到用户态–>内核态之间的切换,这样的成本是比较高的),也就是说,加锁虽然能保证线程安全,但是每次都要加锁资源开销就会比较大

package Threading;
class SingletonLazy {
    private static SingletonLazy instance = null;//先不实例化

    public static SingletonLazy getInstance() {
        if (instance == null) {
            synchronized (SingletonLazy.class) { //锁对象是类对象
                if (instance == null) {  //首次调用getSingleLazy时,才会实例化
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private  SingletonLazy(){

    }
}
public class Demo21 {
    public static void main(String[] args) {

    }
}

此时就可以在前面再加一个 if 判断,来确定是不是要加锁

这两个if 是不一样的含义

这两个if中间隔着一个加锁操作,加锁就意味着锁竞争, 锁竞争就意味着可能会导致阻塞,一旦阻塞,就不知道什么时候能唤醒,所以两个if的结果可能是完全不一样的, 第一个if成立,不一定第二个if能成立

第一个if : 判定是否要加锁

第二个if : 判定是否要创建线程

public static SingletonLazy getInstance() {
    if (instance != null) {
        return instance;
    }
        synchronized (SingletonLazy.class) { //锁对象是类对象
            if (instance == null) {  //首次调用getSingleLazy时,才会实例化
                instance = new SingletonLazy();
            }
    }
    return instance;
}

这样写也是可以的

但是,上面的代码还是有问题的, 在new 对象的时候, new 操作也是分为三个步骤的

new 对象的三个操作:

  1. 申请内存, 得到内存地址
  2. 调用构造方法, 来初始化实例
  3. 把内存的首地址赋值给instance 引用

2 3 两个步骤可能会调换顺序,单线程的时候没事,多线程的时候,要是先执行3 再执行2 , 得到的就是对象只有内存, 内存上的数据无效, getInstance 就会认为对象非空, 直接就返回了,这就是指令重排序问题

要想要禁止指令重排序, 可以使用volatile

其实,多线程的时候, 有的线程在读,有的线程在修改,此时也是有可能出现内存可见性问题的,也是要使用volatile

package Threading;
class SingletonLazy {
    private volatile static SingletonLazy instance = null;//先不实例化

    public static SingletonLazy getInstance() {
        if(instance == null){
            synchronized (SingletonLazy.class) { //锁对象是类对象
                if (instance == null) {  //首次调用getSingleLazy时,才会实例化
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private  SingletonLazy(){

    }
}
public class Demo21 {
    public static void main(String[] args) {

    }
}

以上的代码就是一个完整的单例模式的懒汉实现(十分重要)

主要的要点有三个:

  1. synchronized 加在哪里
  2. 为什么要加两个if , 两个if 的含义
  3. 为什么要加volatile

线程安全版本的单例模式一定要会写!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值