单例模式(线程案例)
单例模式可以分为两种:1.饿汉模式 2.懒汉模式
一.饿汉模式
//饿汉模式👇
class MySingleTon{
//因为这是一个静态成员变量,在类加载的时候,就创建了
private static MySingleTon mySingleTon = new MySingleTon();
//创建一个静态方法,方便在类外获取对象
public static MySingleTon getMySingleTon() {
return mySingleTon;
}
//创建一个私有的构造方法
//避免通过new关键字来调用构造方法,进而创建实例
//保证了唯一性
private MySingleTon(){
}
}
public class ThreadDemo8 {
public static void main(String[] args) {
MySingleTon s1 = MySingleTon.getMySingleTon();
MySingleTon s2 = MySingleTon.getMySingleTon();
System.out.println(s1 == s2);
}
}
程序运行的结果
结果是true,说明了这两次都是获取了同一个实例!,这也说明成功实现了单例模式
因为这个对象是在类加载的时候创建,类加载是单线程的,所以线程安全。但是如果在代码中,没有使用对象,就会造成资源浪费!
二.懒汉模式
这是实现懒汉模式的代码👇
class MySingleTonLazy{
private static MySingleTonLazy mySingleTonLazy = null;
public static MySingleTonLazy getMySingleTonLazy() {
if(mySingleTonLazy != null){
mySingleTonLazy = new MySingleTonLazy();
}
return mySingleTonLazy;
}
//私有的构造方法
private MySingleTonLazy(){
}
}
public class ThreadDemo9 {
public static void main(String[] args) {
MySingleTonLazy s1 = MySingleTonLazy.getMySingleTonLazy();
MySingleTonLazy s2 = MySingleTonLazy.getMySingleTonLazy();
System.out.println(s1==s2);
}
}
这个代码是有两个问题的:1.线程安全问题。2.指令重排序问题
1.线程安全问题
所以说,需要对其加锁。把这些指令打包成一个原子(整体),这样就不会出现这种情况了。
这是修改后的代码,是线程安全的!
第一个if是用来判断这个实例是否已经创建了,如果创建就直接跳过这个代码了。👆
2.指令重排序问题
mySingleTonLazy = new MySingleTonLazy()
这行代码在执行的时候,会分为3个步骤:
1.分配内存空间
2.初始化对象
3.将引用指向对象
这3个步骤可以是按132顺序执行,也可以是123顺序执行👇
如果是按132来执行的,先执行完1,然后执行3 ,在执行完3后,另一个线程也来执行,因为已经创建了对象,在if判断完后,直接跳过去了,但此时这个对象没有初始化,故而会有空指针问题
那么我们该如何解决这个问题👇
引入volatile关键字
1.防止内存可见性问题
2.解决指令重排序问题
此时这个volatile 解决指令重排序问题
private volatile static MySingleTonLazy mySingleTonLazy = null;
那么这懒汉模式正确代码如下👇
class MySingleTonLazy {
// 使用 volatile 修饰,保证可见性和禁止指令重排序
private volatile static MySingleTonLazy mySingleTonLazy = null;
private static Object locker = new Object();
public static MySingleTonLazy getMySingleTonLazy() {
// 第一次检查,判断实例是否已经创建
if (mySingleTonLazy == null) {
// 加锁,保证线程安全
synchronized (locker) {
// 第二次检查,防止多个线程同时通过第一次检查
if (mySingleTonLazy == null) {
mySingleTonLazy = new MySingleTonLazy();
}
}
}
return mySingleTonLazy;
}
// 私有的构造方法,防止外部实例化
private MySingleTonLazy() {
}
}
public class ThreadDemo9 {
public static void main(String[] args) {
MySingleTonLazy s1 = MySingleTonLazy.getMySingleTonLazy();
MySingleTonLazy s2 = MySingleTonLazy.getMySingleTonLazy();
System.out.println(s1 == s2);
}
}
总结:
线程安全:
饿汉模式:天生都线程安全,不用如何处理
懒汉模式:线程不安全,需要自己处理
资源利用率:
饿汉模式:那么创建出了这个实例,然后并没有使用这个实例,这就造成了资源浪费的情况
懒汉模式:用的时候,创建实例,避免不必要的资源浪费的情况。