Java单实例模式的正确写法

前言:
代码简洁与性能高效无法两全其美,本文章专注于大并发程序的性能,如果您追求代码简洁,本文章可能不太适合,因为本文章主要讨论如何写出在高并发下也能运行很好的代码。

 

并文章属于Java并发编程实战中例子。但结合实际场景进行了阐述。

通常,我们如果写一个单实例模式的对象,一般会这样写:

写法一:

 

Java代码  收藏代码
  1. public class Singleton {  
  2.     private static final Singleton instance = new Singleton();  
  3.     /** 
  4.      * 防止其他人new对象 
  5.      */  
  6.     private Singleton(){  
  7.         System.out.println("init");  
  8.     }  
  9.     public static Singleton getInstance(){  
  10.         return instance;  
  11.     }  
  12. }  

 

 

这种方式叫饥饿式单实例,意思是说,不管你用不用这个类的方法,我都把这个类需要的一切资源都分配好。但这样写有一个问题,就是如果这类需要的资源比较多,在系统启动的时候,就会很慢。

因此要求有懒汉式单实例,于是就出现了第二中写法,

写法二:

 

Java代码  收藏代码
  1. public class Singleton {  
  2.     private static Singleton instance = null;  
  3.     /** 
  4.      * 防止其他人new对象 
  5.      */  
  6.     private Singleton(){  
  7.         System.out.println("init");  
  8.     }  
  9.     public static Singleton getInstance(){  
  10.         if(instance == null){  
  11.             instance = new Singleton();  
  12.         }  
  13.         return instance;  
  14.     }  
  15. }  

 这种方式叫懒汉式单实例,即通常所说的延迟加载。这样,在系统启动的时候,不会加载类所需要的各种资源,只有真正使用的时候才去加载各种资源。

 

但这种方法马上就可以看出问题,因为在多线程情况下,可能会导致重复初始化的问题(不明白这个道理,那您需要补充一下同步及多线程知识了)。于是有了改进版,即目前网上比较流行的写法。

写法三:

 

Java代码  收藏代码
  1. public class Singleton {  
  2.     private static Singleton instance = null;  
  3.     /** 
  4.      * 防止其他人new对象 
  5.      */  
  6.     private Singleton(){  
  7.         System.out.println("init");  
  8.     }  
  9.     public static synchronized Singleton getInstance(){  
  10.         if(instance == null){  
  11.             instance = new Singleton();  
  12.         }  
  13.         return instance;  
  14.     }  
  15. }  

 加上关键字synchronized,可以保证只有一个线程在执行这个方法。这个方法至此应该说是比较完美的了,但是,专家不这么认为,在高并发多线程的访问系统中,synchronized关键字会让程序的吞吐量急剧下降,因此,在高并发系统中,应该尽量避免使用synchronized锁。

但这并不能难住我们聪明的软件工程师,有人便写出了双重锁的程序。方法如下:

写法四:

Java代码  收藏代码
  1. public class Singleton {  
  2.     private static Singleton instance = null;  
  3.     /** 
  4.      * 防止其他人new对象 
  5.      */  
  6.     private Singleton(){  
  7.         System.out.println("init");  
  8.     }  
  9.     public static  Singleton getInstance(){  
  10.         if(instance == null){  
  11.             synchronized(Singleton.class){  
  12.                 if(instance == null){  
  13.                     instance = new Singleton();  
  14.                 }  
  15.             }  
  16.         }  
  17.         return instance;  
  18.     }  
  19. }  

这样,通常获得单实例引用是没有锁的,只有第一次初始化时才会加锁,而且如果多个线程进入临界区区后,理论上只有第一个进入临界区的线程才会初始化对象,之后进入临界区的线程因为之前的线程已经初始化,就不会再次进行初始化。

但专家怎么说呢?这个代码有问题。首先,这个程序对同步的应用很到位,即当进入synchronied区,只有一个线程在访问Singleton类。但却忽略了变量的可见性。因为在没有同步的保护下,instance的值在多个线程中可能都是空的,因为即便第一个线程对类进行了初始化,并把类的引用赋值给了instance变量,但也不能保证instance变量的值对其他线程是可见的,因为变量instance没有采用同步的机制。

在java5之后,可以在instance前面添加volatile关键字来解决这个问题,但是这种双重锁的方式已经不建议使用。

 

那么,看看大师推荐的写法吧,见 Java Concurrency In Practice的List 16.6代码:

写法五:

Java代码  收藏代码
  1. public class Singleton {  
  2.     private static class SingletonHolder {  
  3.         public static Singleton resource = new Singleton();  
  4.     }  
  5.     public static Singleton getResource() {  
  6.         return  SingletonHolder.resource ;  
  7.     }  
  8.       
  9.     private Singleton(){  
  10.           
  11.     }  
  12. }  

 

综上各种写法,发现写法一虽然在启动时会让系统启动的慢一些,但却不失为一种高效的写法,当然,如果确实对系统启动时的速度要求高的话,则应该考虑写法五了。

 
对这类话题感兴趣?欢迎发送邮件至donlianli@126.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值