`
donlianli
  • 浏览: 339161 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
Group-logo
Elasticsearch...
浏览量:217938
社区版块
存档分类
最新评论

Java单实例模式

阅读更多

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

 

并文章属于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程序或者开发一个产品时,这种模式还是很重要的。综上所述,我个人比较推荐写法五和写法一,写法七怎么看着也别扭。

另外感谢大家的讨论,这个话题先到这儿吧,我写本文章的主要目的是为了纠正写法三的错误。不知道你的项目中是否还存在写法三的代码呢?

 
对性能感兴趣?请查看 并发编程 系列文章,持续更新中
对这类话题感兴趣?欢迎发送邮件至donlianli@126.com
关于我:邯郸人,擅长Java,Javascript,Extjs,oracle sql。
更多我之前的文章,可以访问:http://hi.baidu.com/donlian

 

6
6
分享到:
评论
13 楼 IT民工% 2013-07-04  
线程安全全
harry775 写道
IT民工% 写道
没看懂第五种写法,求详解


内部类加载和实例创建、线程安全全是由classloader控制。

线程安全全怎么控制的?
12 楼 harry775 2013-07-04  
IT民工% 写道
没看懂第五种写法,求详解


内部类加载和实例创建、线程安全全是由classloader控制。
11 楼 harry775 2013-07-04  
呵呵,这几种单例在  java程序性能优化里面也有。在并发编程实战里面也有。可以关注下,我也在看并发编程实战,之前吧java程序性能优化看了  希望可以和楼主一起学习下、企鹅 25328291
10 楼 a5728238 2013-07-04  
单例对象如果需要持久化呢,楼主还是写下继承Serializable怎么保证对象的唯一吧。枚举是天生的单例,这个也是经常使用到的。
9 楼 sswh 2013-07-04  
BlueGuitar 写道


还是不一样的。第五种要在SingletonHolder 被加载的时候才会初始化。而SingletonHolder只有在getInstance的时候才会加载。

第一种的话,只要Singleton加载就会初始化,即使没有调用getInstance方法。

一段代码里面如果写了getInstance 但是没有执行的情况下就有区别了。



是这样的,但是差别不大。 两种写法本质相同。

第1种是Singleton类被加载时初始化,第5种是SingletonHolder类被加载时初始化。

Singleton类本身就是   只有在被使用到的情况下才加载。

第5种的唯一有用的地方就是:  加载了Singleton类,但是却不调用getInstance()方法的场合。  仔细想一下,这种场合存在吗?

所以,基本没用。
8 楼 BlueGuitar 2013-07-04  
sswh 写道


这段代码中,new Singleton()发生在Singleton 类被加载时。
所以,所谓的第1种方法会导致系统启动变慢是不靠谱的。
并不是所有的第1种写法的Singleton对象都会在系统启动时创建,要看这个类有没有被使用,有没有被加载。

第5种所谓的大师的写法,本质上就是第1种。只在使用到的时候才加载目标类,才初始化singleton对象。


还是不一样的。第五种要在SingletonHolder 被加载的时候才会初始化。而SingletonHolder只有在getInstance的时候才会加载。

第一种的话,只要Singleton加载就会初始化,即使没有调用getInstance方法。

一段代码里面如果写了getInstance 但是没有执行的情况下就有区别了。
7 楼 tjpdj1988 2013-07-04  
那个,用枚举的方式实现单例是不是更好写?
6 楼 Tyrion 2013-07-04  
直接用枚举啊
5 楼 evanzzy 2013-07-04  
系统启动的快慢,不是一个很大的问题,大型系统启动半个小时都是正常的,主要是保证运行时效率高。其实第一种写法足够了。或者……直接Spring
4 楼 sswh 2013-07-04  

对于第1种,文中是这样说的:

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



对于第5种,文中又是这样说的:
引用
那么,看看大师推荐的写法吧,见 Java Concurrency In Practice的List 16.6代码


其实第1种和第5种压根没区别。

引用
private static final Singleton instance = new Singleton();


这段代码中,new Singleton()发生在Singleton 类被加载时。
所以,所谓的第1种方法会导致系统启动变慢是不靠谱的。
并不是所有的第1种写法的Singleton对象都会在系统启动时创建,要看这个类有没有被使用,有没有被加载。

第5种所谓的大师的写法,本质上就是第1种。只在使用到的时候才加载目标类,才初始化singleton对象。
3 楼 thc1987 2013-07-04  
楼主有时间给大家介绍下用枚举来实现单例吧
2 楼 rox 2013-07-04  
晕,double check都不建议使用了。
技术发展太快了。
1 楼 IT民工% 2013-07-03  
没看懂第五种写法,求详解

相关推荐

    java单例模式实例

    单例模式是软件设计模式中的一种经典模式,用于确保一个类只有一个实例,并提供一个全局访问点。在Java中,有多种实现单例模式的方法,每种都有其特点和适用场景。接下来,我们将深入探讨这些实现方式。 首先,我们...

    JAVA中单例模式的几种实现方式.doc

    为了保证单例模式在多线程环境下的正确性,可以在`getInstance()`方法上加上`synchronized`关键字,以确保同一时间只有一个线程能够执行该方法,从而避免创建多个实例。 ```java public class Singleton { private...

    java 中单例模式饿汉式与懒汉式的对比

    java 中单例模式是保证一个类仅有一个实例,并提供一个访问它的全局访问点的设计模式。单例模式有以下特点:单例类只能有一个实例,单例类必须自己自己创建自己的唯一实例,单例类必须给所有其他对象提供这一实例。 ...

    java面向对象编程单实例模式解析

    ### Java面向对象编程中的单实例(Singleton)模式解析 #### 概述 在Java面向对象编程中,单实例模式(也常称为单例模式或Singleton模式)是一种常用的创建型设计模式,它确保一个类只有一个实例,并提供一个全局...

    Java多线程编程环境中单例模式的实现

    ### Java多线程编程环境中单例模式的实现 #### 概述 单例模式是一种常用的软件设计模式,它确保一个类只有一个实例,并提供一个全局访问点。在Java中,单例模式的应用非常广泛,特别是在资源管理、日志记录、...

    java设计模式之多类java-设计模式之:多例(Multiton)模式

    java设计模式之多例(Multiton)模式是对象的创建模式之一,多例模式中的多例类可以有多个实例,且多例类必须自己创建、管理自己的实例,并向外界提供自己的实例。多例模式的特点是:多例类可以有多个实例,多例类必须...

    设计模式——单实例模式、单件模式——Java编写

    单实例模式是软件设计模式中的一种,它的核心思想是确保一个类在整个系统运行过程中只有一个实例存在,并提供一个全局访问点,以保证所有对该类对象的访问都指向这个唯一的实例。这种模式在Java中广泛应用于配置管理...

    java 单工厂模式

    在Java中,单工厂模式通常用于那些需要唯一实例的资源管理器,如打印机Spooler、通信端口管理或属性文件管理。这种模式的主要目的是限制实例化次数,防止并发环境下多个实例的产生,同时提供一个全局的访问点,方便...

    java 设计模式 观察者模式 简单实例 包括测试test类

    在Java中,观察者模式的实现通常基于Java的内置接口`java.util.Observer`和`java.util.Observable`。下面将详细解释观察者模式的概念、结构以及如何在Java中应用这个模式。 **观察者模式的核心概念:** 1. **主题...

    JAVA设计模式之代理模式实例

    在本实例中,我们将深入探讨Java中的代理模式及其应用。 代理模式的核心思想是为一个对象创建一个代理对象,这个代理对象在客户端和目标对象之间起到中介的作用。它可以控制目标对象的访问,也可以在调用目标对象的...

    java三种工厂模式文档+三个实例.rar

    Java工厂模式是面向对象设计模式中的一个重要概念,它提供了一种创建对象的最佳方式。在工厂模式中,我们创建对象时不直接实例化具体类,而是通过一个接口或者抽象类来间接创建。这种模式的核心在于将对象的创建过程...

    重学java的设计模式

    行为型模式有责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式和访问者模式。这些模式主要关注如何在对象间传递消息、控制流程以及执行特定...

    JAVA3D官方实例

    7. **渲染(Rendering)**:JAVA3D提供了多种渲染模式,如颜色、纹理、深度测试等,以控制3D图像的绘制方式。 8. **用户交互(User Interaction)**:可以添加鼠标和键盘监听器,使用户能够与3D场景互动,如旋转、...

    java观察者模式实例

    在这个实例中,我们将深入理解如何在Java中实现观察者模式,以及它如何利用继承和多态性来增强概念理解。 首先,观察者模式的核心思想是定义对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的...

    java设计模式代码实例

    Java设计模式是面向对象编程中的重要概念,它们是解决常见问题的经验总结,为软件开发提供了可复用的解决方案。在给定的压缩包文件中,包含了多种设计模式的代码实例,我们将逐一探讨这些模式及其应用。 1. **策略...

    java 命令模式实例 (设计模式)

    在Java中,命令模式的应用非常广泛,尤其在需要解耦调用者和接收者时。 命令模式的核心组成部分包括:**命令接口**、**具体命令类**、**接收者**和**调用者**。 1. **命令接口**:定义了一个接收者需执行的操作,...

    java设计模式pdf

    ### Java设计模式详解 #### 一、背景与概念 在软件工程领域,设计模式是一种用于解决常见问题的可重用解决方案。《Java设计模式PDF》是一本由James W. Cooper编写的经典书籍,该书详细介绍了Java编程语言中的设计...

    【Java设计模式】多例模式

    Java中的多例模式确保一个类只有唯一命名的实例,并提供对它们的全局访问点。每个命名实例都通过一个唯一的键进行访问,使其成为Java设计模式的重要组成部分。 ## 二、详细解释及实际示例 1. **实际示例**: - 多...

    Java企业设计模式

    Java企业设计模式是软件开发中不可或缺的一部分,它们是经过时间考验、被广泛接受的解决方案模板,用于解决在大型企业级应用程序开发中常见的问题。这些模式提供了可重用的结构,帮助开发者更有效地组织代码,提高...

Global site tag (gtag.js) - Google Analytics