栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java单例模式

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java单例模式

(1)饿汉式(立即加载):

饿汉式单例在类加载初始化时就创建好一个静态的对象供外部使用,除非系统重启,这个对象不会改变,所以本身就是线程安全的。

Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。(事实上,通过Java反射机制是能够实例化构造方法为private的类的,会使Java单例实现失效)

  1. package com.atguigu.interview.chapter02;  
  2.   
  3.   
  4. public class Singleton1 {  
  5.   
  6.       
  7.     private Singleton1() {  
  8.         System.out.println("构造函数Singleton1");  
  9.     }  
  10.   
  11.       
  12.     private static Singleton1 single = new Singleton1();  
  13.   
  14.       
  15.     public static Singleton1 getInstance() {  
  16.         System.out.println("getInstance");  
  17.         return single;  
  18.     }  
  19.   
  20.     public static void main(String[] args){  
  21.         System.out.println("初始化");  
  22.         Singleton1 instance = Singleton1.getInstance();  
  23.     }  
  24. }  

  1. 懒汉式(延迟加载):

该示例虽然用延迟加载方式实现了懒汉式单例,但在多线程环境下会产生多个Singleton对象

  1. package com.atguigu.interview.chapter02;  
  2.   
  3.   
  4. public class Singleton2 {  
  5.   
  6.       
  7.     private Singleton2() {  
  8.         System.out.println("构造函数Singleton2");  
  9.     }  
  10.   
  11.       
  12.     private static Singleton2 single = null;  
  13.   
  14.       
  15.     public static Singleton2 getInstance() {  
  16.         if(single == null){  
  17.             System.out.println("getInstance");  
  18.             single = new Singleton2();  
  19.         }  
  20.         return single;  
  21.     }  
  22.   
  23.     public static void main(String[] args){  
  24.   
  25.         System.out.println("初始化");  
  26.         Singleton2 instance = Singleton2.getInstance();  
  27.     }  
  28. }  

  1. 同步锁(解决线程安全问题):

在方法上加synchronized同步锁或是用同步代码块对类加同步锁,此种方式虽然解决了多个实例对象问题,但是该方式运行效率却很低下,下一个线程想要获取对象,就必须等待上一个线程释放锁之后,才可以继续运行。

  1. package com.atguigu.interview.chapter02;  
  2.   
  3.   
  4. public class Singleton3 {  
  5.   
  6.       
  7.     private Singleton3() {}  
  8.   
  9.       
  10.     private static Singleton3 single = null;  
  11.   
  12.     public static Singleton3 getInstance() {  
  13.   
  14.         // 等同于 synchronized public static Singleton3 getInstance()  
  15.         synchronized(Singleton3.class){  
  16.             // 注意:里面的判断是一定要加的,否则出现线程安全问题  
  17.             if(single == null){  
  18.                 single = new Singleton3();  
  19.             }  
  20.         }  
  21.         return single;  
  22.     }  
  23. }  

(4)双重检查锁(提高同步锁的效率):

使用双重检查锁进一步做了优化,可以避免整个方法被锁,只对需要锁的代码部分加锁,可以提高执行效率。

  1. package com.atguigu.interview.chapter02;  
  2.   
  3.   
  4. public class Singleton4 {  
  5.   
  6.       
  7.     private Singleton4() {}  
  8.   
  9.       
  10.     private static Singleton4 single = null;  
  11.   
  12.       
  13.     public static Singleton4 getInstance() {  
  14.         if (single == null) {  
  15.             synchronized (Singleton4.class) {  
  16.                 if (single == null) {  
  17.                     single = new Singleton4();  
  18.                 }  
  19.             }  
  20.         }  
  21.         return single;  
  22.     }  
  23. }  
重点:双重检查锁需不需要加volatile?

在双重检查锁的情况下,依旧会有Bug的产生

怎么产生呢,假定当一万个数据不断请求获取single,第一次判断single == null以后,有某一个请求拿到锁,然后进行逻辑操作,在 single = new Singleton4();  这一步的时候,new对象初始化,会先new一个地址值,

然后通过init初始化,

然后通过astore_1进行赋值,

[假定我们想要给到赋值为3]

我们知道,单线程情况下,当不影响最终结果一致性的情况下,指令是会重排序的。

就比如:x=1,

               y=x+1

这个情况,两者就不能进行重排序,因为后面的操作依赖于前面的操作。

但是,通过init初始化,和通过astore_1进行赋值并没有互相依赖的情况,

所以是会造成指令重排序的。

所以,当我请求1走到new对象的时候,有可能初始化数据为0,还没有给到3这个数值,

而请求10来到了第一个判断

  1.   public static Singleton4 getInstance() {  
  2.         if (single == null) {  
  3.             synchronized (Singleton4.class) {  
  4.                 if (single == null) {  
  5.                     single = new Singleton4();  
  6.                 }  
  7.             }  
  8.         }  
  9.         return single;  
  10.     }  
  11. }  

此时就会判断,当前的sinfgle的数值为0,为什么呢?

因为我们  if (single == null)  判断,和synchronized 加锁里面的操作,是并行执行的。并不会互相影响。

所以当请求10拿到数据为0以后,就不会进入阻塞,而会进行后续的代码。形成数据不一致问题。

产生Bug

那么,在 private static Singleton4 single = null;  里面就需要添加volatile了,

那么,为什么添加了volatile以后,就一定可以限制指令重排序呢?

我们知道,CPU并发控制:

防止打断的四种方式:

  • 关中断
  • 缓存一致性
  • 系统屏障 [编译级别、指令级别]
  • 总线/缓存锁

在我们的系统上,通过读取CPU的防打断策略,得到自身的方法。

而我们的volatile,是由C++写的,它的底层是通过内存屏障限制CPU的方式,进行防打断,

volatile有内存屏障

volatile 写

在每个 volatile 写操作的前面插入一个 StoreStore 屏障。在每个 volatile 写操作的后面插入一个 StoreLoad 屏障。

2、volatile 读

在每个 volatile 读操作的后面插入一个 LoadLoad 屏障和一个 LoadStore 屏障。

 

 添加了volatile以后,可以实现可见性,和禁止指令重排序。

(5) 静态内部类:

这种方式引入了一个内部静态类(static class),静态内部类只有在调用时才会加载,它保证了Singleton 实例的延迟初始化,又保证了实例的唯一性。它把singleton 的实例化操作放到一个静态内部类中,在第一次调用getInstance() 方法时,JVM才会去加载InnerObject类,同时初始化singleton 实例,所以能让getInstance() 方法线程安全。

特点是:即能延迟加载,也能保证线程安全。

静态内部类虽然保证了单例在多线程并发下的线程安全性,但是在遇到序列化对象时,默认的方式运行得到的结果就是多例的。

  1. package com.atguigu.interview.chapter02;  
  2.   
  3.   
  4. public class Singleton5 {  
  5.   
  6.       
  7.     private Singleton5() {}  
  8.   
  9.       
  10.     private static class InnerObject{  
  11.         private static Singleton5 single = new Singleton5();  
  12.     }  
  13.   
  14.     public static Singleton5 getInstance() {  
  15.         return InnerObject.single;  
  16.     }  
  17. }  

(6)内部枚举类实现(防止反射攻击):

事实上,通过Java反射机制是能够实例化构造方法为private的类的。这也就是我们现在需要引入的枚举单例模式。

  1. package com.atguigu.interview.chapter02;  
  2.   
  3.   
  4. public class SingletonFactory {  
  5.   
  6.       
  7.     private enum EnumSingleton{  
  8.         Singleton;  
  9.         private Singleton6 singleton;  
  10.   
  11.         //枚举类的构造方法在类加载是被实例化  
  12.         private EnumSingleton(){  
  13.             singleton = new Singleton6();  
  14.         }  
  15.         public Singleton6 getInstance(){  
  16.             return singleton;  
  17.         }  
  18.     }  
  19.       
  20.     public static Singleton6 getInstance() {  
  21.         return EnumSingleton.Singleton.getInstance();  
  22.     }  
  23. }  
  24.   
  25. class Singleton6 {  
  26.     public Singleton6(){}  

} 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/943750.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号