前言:
代码简洁与性能高效无法两全其美,本文章专注于大并发程序的性能,如果您追求代码简洁,本文章可能不太适合,因为本文章主要讨论如何写出在高并发下也能运行很好的代码。
并文章属于Java并发编程实战中例子。但结合实际场景进行了阐述。
通常,我们如果写一个单实例模式的对象,一般会这样写:
写法一:
public class Singleton { private static final Singleton instance = new Singleton(); /** * 防止其他人new对象 */ private Singleton(){ System.out.println("init"); } public static Singleton getInstance(){ return instance; } }
这种方式叫饥饿式单实例,意思是说,不管你用不用这个类的方法,我都把这个类需要的一切资源都分配好。但这样写有一个问题,就是如果这类需要的资源比较多,在系统启动的时候,就会很慢。
因此要求有懒汉式单实例,于是就出现了第二中写法,
写法二:
public class Singleton { private static Singleton instance = null; /** * 防止其他人new对象 */ private Singleton(){ System.out.println("init"); } public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
这种方式叫懒汉式单实例,即通常所说的延迟加载。这样,在系统启动的时候,不会加载类所需要的各种资源,只有真正使用的时候才去加载各种资源。
但这种方法马上就可以看出问题,因为在多线程情况下,可能会导致重复初始化的问题(不明白这个道理,那您需要补充一下同步及多线程知识了)。于是有了改进版,即目前网上比较流行的写法。
写法三:
public class Singleton { private static Singleton instance = null; /** * 防止其他人new对象 */ private Singleton(){ System.out.println("init"); } public static synchronized Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
加上关键字synchronized,可以保证只有一个线程在执行这个方法。这个方法至此应该说是比较完美的了,但是,专家不这么认为,在高并发多线程的访问系统中,synchronized关键字会让程序的吞吐量急剧下降,因此,在高并发系统中,应该尽量避免使用synchronized锁。
但这并不能难住我们聪明的软件工程师,有人便写出了双重锁的程序。方法如下:
写法四:
public class Singleton { private static Singleton instance = null; /** * 防止其他人new对象 */ private Singleton(){ System.out.println("init"); } public static Singleton getInstance(){ if(instance == null){ synchronized(Singleton.class){ if(instance == null){ instance = new Singleton(); } } } return instance; } }
这样,通常获得单实例引用是没有锁的,只有第一次初始化时才会加锁,而且如果多个线程进入临界区区后,理论上只有第一个进入临界区的线程才会初始化对象,之后进入临界区的线程因为之前的线程已经初始化,就不会再次进行初始化。
但专家怎么说呢?这个代码有问题。首先,这个程序对同步的应用很到位,即当进入synchronied区,只有一个线程在访问Singleton类。但却忽略了变量的可见性。因为在没有同步的保护下,instance的值在多个线程中可能都是空的,因为即便第一个线程对类进行了初始化,并把类的引用赋值给了instance变量,但也不能保证instance变量的值对其他线程是可见的,因为变量instance没有采用同步的机制。
在java5之后,可以在instance前面添加volatile关键字来解决这个问题,但是这种双重锁的方式已经不建议使用。
那么,看看大师推荐的写法吧,见 Java Concurrency In Practice的List 16.6代码:
写法五:
public class Singleton { private static class SingletonHolder { public static Singleton resource = new Singleton(); } public static Singleton getResource() { return SingletonHolder.resource ; } private Singleton(){ } }
综上各种写法,发现写法一虽然在启动时会让系统启动的慢一些,但却不失为一种简洁而高效的写法,当然,如果确实对系统启动时的速度要求高的话,则应该考虑写法五了。
另外,其实单实例方法还有好多种,在effective Java中有写到:
写法六:
public class Singleton { public static final Singleton INSTANCE = new Singleton(); private Singleton(){} public void method(){ //... } public static void main(String[] a){ //调用方法。 Singleton.INSTANCE.method(); } }
写法七:
/** * 利用枚举巧妙创建单实例 */ public enum Singleton { INSTANCE; public void method(){ //... } public static void main(String[] a){ //调用方法。 Singleton.INSTANCE.method(); } }
另外,双重锁的方式,在加上volatile关键字后,也是高效安全的写法。
写法八:
public class Singleton { private static volatile Singleton instance = null; /** * 防止其他人new对象 */ private Singleton(){ System.out.println("init"); } public static Singleton getInstance(){ if(instance == null){ synchronized(Singleton.class){ if(instance == null){ instance = new Singleton(); } } } return instance; } }
其实,在今天spring大行其道的天下,单实例需求已经不多,spring中的bean默认都是单实例。但是要做一些app程序或者开发一个产品时,这种模式还是很重要的。综上所述,我个人比较推荐写法五和写法一,写法七怎么看着也别扭。
另外感谢大家的讨论,这个话题先到这儿吧,我写本文章的主要目的是为了纠正写法三的错误。不知道你的项目中是否还存在写法三的代码呢?
相关推荐
一个简单的java工程,包含注释,一目了然,其中包含了单例模式的所有实现方式,懒汉式,饿汉式,双重校验,枚举,静态内部类等方式实现单例。
设计模式之单实例模式,用java实现的单件模式(单实例模式),里面附有详细的注释!
Java多线程编程环境中单例模式的实现
主要介绍了Java设计模式之监听器模式,结合实例形式较为详细的分析了java设计模式中监听器模式的概念、原理及相关实现与使用技巧,需要的朋友可以参考下
JAVA设计模式之代理模式实例
主要介绍了Java中单例模式详解,单例模式包括了懒汉式单例、饿汉式单例、登记式单例三种,想要了解的朋友可以了解一下。
java 设计模式 观察者模式 简单实例 包括测试test类
主要介绍了java 中单例模式饿汉式与懒汉式的对比的相关资料,这里对这两种单例模式进行对比,希望大家能理解并应用,需要的朋友可以参考下
java23种设计模式+23个实例demo: 关于代码的几点说明: 1. 代码为根据个人对Design Pattern的学习理解写出, 2. 每个Pattern均是一个目录,其目录名即是Pattern的名字, 所有代码用JDeveloper 9i 和JBuilder 6.0编写...
主要介绍了Java中单例模式的7种写法,本文分别给出每种方式的实现代码,需要的朋友可以参考下
java设计模式实例,以代码的形式展示设计模式的魅力,是程序员能够更直观的理解,同时免去了大量阅读晦涩书本的过程,能够通过实例直接扩展训练,非常方便,你值得下载~
通过单例模式实例化获取propertyUtil 工具包实例,高效加载配置文件,java语言编写。通过单例模式实例化获取propertyUtil 工具包实例,高效加载配置文件,java语言编写。通过单例模式实例化获取propertyUtil 工具包...
单例模式的扩展及应用。 编写一个类LimitInstanceClass,该类最多可以实例化指定个数实例。实例的个数用配置文件InstanceLimit.cfg指定。例如,如果InstanceLimit.cfg的内容为2,则LimitInstanceClass最多可以同时...
用java实现创建者模式,实现UML类图,并且实现一个消息发送的实例
java面向对象编程单实例模式解析,面试被问到的可能性很大.
java23种设计模式代码实例
java三种工厂模式文档+三个实例,里面有个word文档是学习文档 然后配套有三个项目学习
一个Java Dao模式的的具体实例,详细描述了Dao的在实际项目中的开发应用
java-设计模式之:多例(Multiton)模式
以JAVA为例,汇总了十几种常用的设计模式,包括了:单例模式、工厂模式、建造者模式、适配器模式、装饰器模式、外观模式、命令模式、观察者模式、状态模式、策略模式、模板方法模式等。仅供学习使用。 相关文章请看...