单例模式
1、单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点
2、要点:
①构造函数私有化
②private static修饰变量
3、单例模式的四种代码实现:
①饿汉式——线程安全
②懒汉式——线程不安全
③懒汉式——线程安全
④双重锁校验——线程安全
·
饿汉式——线程安全
不管需不需要用到该对象,都会先直接实例化该对象
- 优点:不会产生线程不安全的问题
- 缺点:如果一直用不到该对象,就会资源浪费
class Hungry{
private Hungry(){}
private static Hungry instance=new Hungry();
public static Hungry getInstance(){
return instance;
}
}
·
懒汉式——线程不安全
只有用到该对象,才会去实例化该对象
- 优点:instance的实例化延迟,如果用不到该对象,就不会去实例化该对象,节约资源
- 缺点:线程不安全,多线程的情况下,会多次创建实例
class LazyNo{
private LazyNo(){}
private static LazyNo instance;
public static LazyNo getInstance(){
if (instance==null){
instance=new LazyNo();
}
return instance;
}
}
·
懒汉式——线程安全
跟线程不安全的懒汉式差不多,只是加了个锁
- 优点:线程安全
- 缺点:每次都要先获取锁,再去判断instance是否实例化了,效率过低。
【如果instance已经实例化了,那其实没有必要先去获取锁,再判断,加锁解锁,很影响执行效率】
class Lazy{
private Lazy(){}
private static Lazy instance;
public static synchronized Lazy getInstance(){
if (instance==null){
instance=new Lazy();
}
return instance;
}
}
·
双重校验锁——线程安全
同时使用volatile和synchronized来保证线程安全和效率
- 优点:线程安全,执行效率高
class Second{
private Second(){}
private static volatile Second instance;
public static Second getInstance(){
//先判断instance是否实例化了,不为null,则直接返回instance
if (instance==null){
//获取锁之后再次判断instance是否实例化了
synchronized(Second.class){
if (instance==null){
instance=new Second();
}
}
}
return instance;
}
}
为什么要两次if判断?
①第一个 if 判断是为了,在已经实例化过instance后,可以直接返回instance,不需要再去获取锁。【解决了 懒汉式——线程安全 的缺点】
②第二个 if 判断是为了避免在多线程情况下,多次实例化对象。
● 在多线程情况下,假设线程T1、T2、T3同时判断完了第一个 if,而T1先获取到了锁,T2、T3阻塞。
● T1在第二个 if 中判断instance为null后,T1便实例化instance
● 等待T1执行完毕,T2或T3获取到锁,通过第二个 if 判断得出结果,instance已经实例化了,所以T2或T3就不会实例化instance了,避免了多次实例化对象。
· · · 如果没有第二个 if 判断,T2或T3获取到锁后,就会直接实例化对象了。
·
为什么要用volatile修饰变量?
使⽤ volatile 可以禁⽌ JVM 的指令重排,保证在多线程环境下也能正常运⾏
Instance = new Singleton(); 这段代码其实是分为三步执⾏:
① 为 uniqueInstance 分配内存空间
② 初始化 uniqueInstance
③ 将 uniqueInstance 指向分配的内存地址
· 由于 JVM 具有指令重排的特性,执⾏顺序有可能变成 1>3>2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致⼀个线程获得还没有初始化的实例。例如,线程 T1 执⾏了 1 和 3,此时 T2 调⽤geteInstance() 后发现 Instance 不为空,因此返回 Instance,但此时 Instance 还未被初始化。
·
·
【不知道我有没有讲清楚,欢迎评论或私信与我交流】