JAVA - 【单例设计模式】构造私有,对象唯一

目录(DCL必须要加volatile)

  • 什么是单例设计模式
  • 两种单例模式比较
  • 实例(Runtime)
  • DCL的引入
  • DCL一定要加volatile


<一> 什么是单例设计模式

确保类在内存中只有一个对象,并且该对象由类自动创建并且对外提供;

<二> 两种单例模式

▶ 饿汉式

  • 私有构造函数
  • 创建私有静态类变量并new赋值
  • 创建公有静态方法 getXxx,内部返回私有静态类变量
package kyleeo.util_01;

/*
 * 单例设计模式一
 */
public class Student {
	private Student() {}
	
	private static Student s = new Student();
	
	public static Student getStudent() {
		return s;
	}
}

▶ 懒汉式

  • 私有构造函数
  • 创建私有静态类变量=null
  • 创建公有静态方法 getXxx,需要使用synchronized约束方法
    • 判断私有静态类变量是否为null,为null则表示第一次创建,new
    • 判断私有静态类变量是否为null,不为null则直接返回私有静态类变量
package kyleeo.util_01;

/*
 * 单例延迟加载模式
 */
public class Teacher {
	private Teacher() {}
	
	private static Teacher t = null;
	
	public static synchronized Teacher getTeacher() {
		if(t == null) {
			t = new Teacher();
		}
		return t;
	}
}

<三> Runtime

1> System.exit(0) 底层调用的是Runtime中的exit()方法

2> Runtime中exit()调用的是Shutdown中的exit()方法

3> Shutdown中的exit()方法又调用了halt()方法

这里的halt()方法将会调用halt0本地方法

4> 这里我们要说道Runtime类,该类是单例模式的典型代表

5> 那么Runtime这个类究竟是干什么的呢?

Runtime类是JVM运行时的代码表示:



2020-10-13补充 ↓



四:DCL的引入

1> 什么是DCL?

Double Check Lock

2> 在上述“懒汉式”单例模式中,直接使用synchronized修饰整个方法(锁的粒度很大,部分业务逻辑也被锁住是不合适的)

public class Teacher {
    private Teacher() {}

    private static Teacher t = null;

    public static synchronized Teacher getTeacher() {
        // 业务逻辑
        // 业务逻辑
        // 业务逻辑
        // 业务逻辑
        if(t == null) {
            t = new Teacher();
        }
        return t;
    }
}

3> 此时我们使用synchronized锁住代码块,而不是锁住整个方法

public class Teacher {
    private Teacher() {}

    private static Teacher t = null;

    public static Teacher getTeacher() {
        // 业务逻辑
        // 业务逻辑
        // 业务逻辑
        // 业务逻辑
        if(t == null) {
            synchronized (Teacher.class) {
                t = new Teacher();
            }
        }
        return t;
    }

    /*
        2040686731
        1052444169
        1052444169
        1052444169
        1052444169
        1052444169
        1052444169
        1052444169
        1052444169
        1052444169
     */
    public static void main(String[] args) {
        for(int i=0;i<10;i++){
            new Thread(()->{
                System.out.println(Teacher.getTeacher().hashCode());
            }).start();
        }
    }
}

4> 上述的代码在多线程环境下仍然是不安全的:

假设线程A执行到getTeacher方法的if(t == null)被线程B抢占,线程B优先级非常高直接将函数执行完(锁也已经释放掉了),此时线程A抢到了CPU开始执行后方的代码,此时已经创建了两个对象,出现了背离单例模式的情况,如何避免呢?于是出现了DCL即双重检测

5> DCL

/*
 * 单例模式DCL
 */
public class Teacher {
    private Teacher() {}

    private static Teacher t = null;

    public static Teacher getTeacher() {
        // 业务逻辑
        // 业务逻辑
        if(t == null) {
            synchronized (Teacher.class) {
                if(t==null) {
                    t = new Teacher();
                }
            }
        }
        return t;
    }
}

五:DCL一定要加volatile(阻止指令重排序)

上方的代码,如果Teacher不加volatile,那么假设当线程A执行到 t = new Teacher(); 语句指令发生了重排序,在astore执行后,B线程发现t对象已经不是null了(半初始化状态),于是B线程又创建了一个Teacher对象,原线程随后也创建了自己的Teacher对象,此时背离了单例模式的情况,如何解决呢?)

→ 使用volatile修饰t对象,volatile有两个功能:

1. 确保线程的可见性(一个线程修改了从主内存中copy的变量会强制刷新到主内存,同时在每次操作volatile变量时会重主内存中重新拷贝一份到当前线程中)

2. 禁止指令重排序

与volatile类比,synchronized可以保持代码的原子性,因为保证了原子性,所以也从另一个角度确保了线程的可见性;

/*
 * 单例模式DCL - 使用volatile禁止指令重排序
 */
public class Teacher {
    private Teacher() {}

    private static volatile Teacher t = null;

    public static Teacher getTeacher() {
        // 业务逻辑
        // 业务逻辑
        if(t == null) {
            synchronized (Teacher.class) {
                if(t==null) {
                    t = new Teacher();
                }
            }
        }
        return t;
    }
}

六:volatile如何禁止指令重排序

禁止指令重排序主要是为了防止多线程读写数据出现异常,所以出现了四种内存屏障:

1. readread

read1  readread  read2

2. readwrite

read1  readwrite  write1

3. writeread

write1  writeread  read1

4. writewrite

write1  writewrite  write2

在内存屏障前后的语句不能跨越内存屏障执行,前面指令执行完毕后后方指令才允许被执行;

但是还有一种更加好理解的方式:直接在汇编层面使用lock指令修饰读写指令,通过控制MC内存控制器产生信号以锁住数据总线,确保当前只有自己线程在操作(JVM中似乎是这种实现)。

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值