Java多线程---单例模式(有趣易懂版)

单例模式

单例对象的类只能允许一个实例存在。

特征
  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。

饿汉模式

在类加载时,完成实例化,用时直接用。可避免线程同步问题。

public class singleTon {     private static final singleTon instance =new singleTon();     private singleTon(){ }     public static singleTon getInstance(){         return instance;     } } 

优点

线程安全

缺点

生命周期较长,造成资源浪费

在类装载的时候就完成实例化,没有达到懒加载的效果。如果从始至终从未使用过或最后才用到这个实例,则会造成内存资源的浪费。

懒汉模式

类加载时,先不实例化,等到用到时再加载。

public class SingleTon {     private static SingleTon instance=null;     private SingleTon(){}     public static SingleTon getInstance(){        if (instance==null){        //用到了,开始实例化            instance=new SingleTon4();         }         return instance;    } } 

但这个方式是线程不安全的,每个线程来到这儿都会new对象,毫不顾忌啊。那怎么办呢?加锁呗!

public class SingleTon {     private static SingleTon instance=null;     private SingleTon(){}     public static SingleTon getInstance(){         synchronized (SingleTon.class) {             if (instance==null){                 //用到了,开始实例化                 instance=new SingleTon();             }         }         return instance;     } } 

那么问题来了

加锁不是锁对象吗,那你为啥不锁instance呢?

因为instance还没实例化啊,懒汉懒汉,它用时才实例化,所以它现在还只是个空引用!

那现在加锁了它就线程安全了吗?

并没有!这里的赋值操作不具有原子性。new 对象实际上是拆分成三个步骤:

1.分配内存 2.执行初始化 3.赋值给变量。

还效率低、资源浪费,每个线程都要先来竞争锁,竞争失败就阻塞。竞争成功了也不知道这instance到底还是不是null,反正现在看着是null,那我就去实例!
不好不好!那怎么办呢?
双重校验锁

public class SingleTon4 { 	//volatile保证共享变量可见性   	private static volatile SingleTon instance=null; 	private SingleTon(){} 	public static SingleTon getInstance(){     	//双重判断:防止多次实例化         if (instance==null) {             synchronized (SingleTon.class) {                 if (instance == null) {                     //synchronized加锁:保证赋值操作原子性                     instance = new SingleTon();                 }             }         }         return instance; 	} } 

那么问题又来了!

这volatile在这儿是干嘛的?

这里体现了它的两个作用:保证可见性、禁止重排序

  • 可见性

在多线程执行中,原本是要将数据从主内存拿到自己的私有工作区中去修改,然后再放回主内存中,但这个过程中,可能A线程正在改数据还没放回去,B线程又去拷贝这个数据去修改,导致数据不一致。
使用volatile关键字,可以让线程们每次都去主存中读取,用完再刷新到主存。

因此,在本例中,第一重判断就限制了很多线程,线程们一看instance已经被实例了,就不会再去竞争锁了,因为共享数据可见,就直接去读取结果并返回。

  • 禁止重排序,建立内存屏障

JVM在执行代码过程中为了优化性能,会有指令重排序的特性。如实例化对象时的三步可能会重排序:
原本:1.分配内存空间 2.初始化 3.赋值给变量
可能顺序被优化为:1–3—2
当1、3执行完后,对方发现变量不为null,就直接返回了,实际上该变量还没初始化完,引用里保存的是无效对象。

静态内部类

一个集 饿汉模式和懒汉模式 优点于一身的方法

class SingleTon3 {     private SingleTon3(){}     private static class Holder{         static SingleTon3 instance=new SingleTon3();     }     public static SingleTon3 getInstance(){         return Holder.instance;     } } 

版权声明:玥玥 发表于 2021-03-21 9:16:03。
转载请注明:Java多线程---单例模式(有趣易懂版) | 女黑客导航