Java单例模式 --- 饿汉式、懒汉式 实例

什么是单例模式

单例模式是保证在整个应用程序的生命周期中,在任何时刻,被指定的类只有一个实例。

 

实现单例模式的思路

1. 外界不能造对象  --把无参构造方法私有

2. 类本身要造一个  --调用构造方法即可

3. 通过公共的方式对外提供

       --通过public修饰

       --又由于无参构造私有,所以要用static修饰符

       --为了保证静态方法只能访问静态成员,所以这个对象也要用static修饰

             如果这样...     static Student s = new Student();

             那么外界可以这样...   Student.s = null使用为null的对象可能出现空指针异常错误

       --所以要加private修饰

这样想着,就有了下面的这种写法:

 

懒汉式,线程不安全

[java]  view plain  copy
  1. public class Singleton {  
  2.       
  3.     private static Singleton instance;  
  4.   
  5.      //外界不能造对象 把无参构造方法私有  
  6.     private Singleton (){}  
  7.   
  8.     //通过公共的方式对外提供  通过public修饰  
  9.     public static Singleton getInstance() {  
  10.      if (instance == null) {  
  11.          //类本身要造一个  调用构造方法即可  
  12.          instance = new Singleton();  
  13.      }  
  14.      return instance;  
  15.     }  
  16. }  

这段代码简单明了,而且使用了懒加载模式,但是却存在致命的问题。当有多个线程并行调用 getInstance()的时候,就会创建多个实例。也就是说在多线程下不能正常工作。

 

饿汉式 static final field

这种方法非常简单,因为单例的实例被声明成 static final变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。

[java]  view plain  copy
  1. public class Singleton{  
  2.     //类加载时就初始化  
  3.     private static final Singleton instance = new Singleton();  
  4.       
  5.     private Singleton(){}  
  6.   
  7.     public static Singleton getInstance(){  
  8.         return instance;  
  9.     }  
  10. }  

这种写法如果完美的话,就没必要在啰嗦那么多双检锁的问题了。缺点是它不是一种懒加载模式(lazy initialization),单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance()之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。

懒汉式的三种写法

 

方法1:懒汉式线程安全

[java]  view plain  copy
  1. public class Singleton{  
  2.     //类加载时就初始化  
  3.     private static final Singleton instance;  
  4.       
  5.     private Singleton(){}  
  6.   
  7.     public static synchronized Singleton getInstance(){  
  8.         if(instance==null){  
  9.           instance = new Singleton();  
  10.         }  
  11.         return instance;  
  12.     }  
  13. }  

虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance()方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。这就引出了双重检验锁。

方式2:双重检验锁

[java]  view plain  copy
  1. public class Singleton {  
  2.     private volatile static Singleton instance; //声明成 volatile 保证编译器不进行优化  
  3.     private Singleton (){}  
  4.   
  5.     public static Singleton getSingleton() {  
  6.         if (instance == null) {                           
  7.             synchronized (Singleton.class) {  
  8.                 if (instance == null) {         
  9.                     instance = new Singleton();  
  10.                 }  
  11.             }  
  12.         }  
  13.         return instance;  
  14.     }  
  15.      
  16. }  

方式3:静态内部类

[java]  view plain  copy
  1. public class Singleton {    
  2.     private static class SingletonHolder {    
  3.         private static final Singleton INSTANCE = new Singleton();    
  4.     }    
  5.     private Singleton (){}    
  6.     public static final Singleton getInstance() {    
  7.         return SingletonHolder.INSTANCE;   
  8.     }    
  9. }  


这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder是私有的,除了 getInstance()之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK版本。


懒汉式、饿汉式在spring IOC中的应用

在spring IOC中,bean在xml中可以配置为singleton,而且有一个lazy-init属性

lazy-init=true,设置延迟初始化, 在创建容器之后,在第一次从容器获取对象的时候创建单例的对象

如果没有配置或延迟初始化为默认值, 单例的对象会在创建容器的时候创建对象


更详细的内容参考大神博客:如何正确地写出单例模式


猜你喜欢

转载自blog.csdn.net/it666dhw/article/details/80622329