版权声明:本文为【viclee】原创,如需转载请注明出处~
Java中单例(Singleton)模式是一种广泛使用的设计模式。单例模式的主要作用是保证在Java程序中,某个类只有一个实例存在。一些管理器和控制器常被设计成单例模式。
单例模式有很多好处,它能够避免实例对象的重复创建,不仅可以减少每次创建对象的时间开销,还可以节约内存空间;能够避免由于操作多个实例导致的逻辑错误。如果一个对象有可能贯穿整个应用程序,而且起到了全局统一管理控制的作用,那么单例模式也许是一个值得考虑的选择。
单例模式有很多种写法,大部分写法都或多或少有一些不足。下面将分别对这几种写法进行介绍。
1、饿汉模式
- public class Singleton{
- private static Singleton instance = new Singleton();
- private Singleton(){}
- public static Singleton newInstance(){
- return instance;
- }
- }
这种实现方式适合单例占用内存比较小,在初始化时就会被用到的情况。但是,如果单例占用的内存比较大,或单例只是在某个特定场景下才会用到,使用饿汉模式就不合适了,这时候就需要用到懒汉模式进行延迟加载。
2、懒汉模式
- public class Singleton{
- private static Singleton instance = null;
- private Singleton(){}
- public static Singleton newInstance(){
- if(null == instance){
- instance = new Singleton();
- }
- return instance;
- }
- }
懒汉模式中单例是在需要的时候才去创建的,如果单例已经创建,再次调用获取接口将不会重新创建新的对象,而是直接返回之前创建的对象。如果某个单例使用的次数少,并且创建单例消耗的资源较多,那么就需要实现单例的按需创建,这个时候使用懒汉模式就是一个不错的选择。但是这里的懒汉模式并没有考虑线程安全问题,在多个线程可能会并发调用它的getInstance()方法,导致创建多个实例,因此需要加锁解决线程同步问题,实现如下。
- public class Singleton{
- private static Singleton instance = null;
- private Singleton(){}
- public static synchronized Singleton newInstance(){
- if(null == instance){
- instance = new Singleton();
- }
- return instance;
- }
- }
3、双重校验锁
加锁的懒汉模式看起来即解决了线程并发问题,又实现了延迟加载,然而它存在着性能问题,依然不够完美。synchronized修饰的同步方法比一般方法要慢很多,如果多次调用getInstance(),累积的性能损耗就比较大了。因此就有了双重校验锁,先看下它的实现代码。
- public class Singleton {
- private static Singleton instance = null;
- private Singleton(){}
- public static Singleton getInstance() {
- if (instance == null) {
- synchronized (Singleton.class) {
- if (instance == null) {//2
- instance = new Singleton();
- }
- }
- }
- return instance;
- }
- }
我们看到双重校验锁即实现了延迟加载,又解决了线程并发问题,同时还解决了执行效率问题,是否真的就万无一失了呢?
这里要提到Java中的指令重排优化。所谓指令重排优化是指在不改变原语义的情况下,通过调整指令的执行顺序让程序运行的更快。JVM中并没有规定编译器优化相关的内容,也就是说JVM可以自由的进行指令重排序的优化。
这个问题的关键就在于由于指令重排优化的存在,导致初始化Singleton和将对象地址赋给instance字段的顺序是不确定的。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。
以上就是双重校验锁会失效的原因,不过还好在JDK1.5及之后版本增加了volatile关键字。volatile的一个语义是禁止指令重排序优化,也就保证了instance变量被赋值的时候对象已经是初始化过的,从而避免了上面说到的问题。代码如下:
- public class Singleton {
- private static volatile Singleton instance = null;
- private Singleton(){}
- public static Singleton getInstance() {
- if (instance == null) {
- synchronized (Singleton.class) {
- if (instance == null) {
- instance = new Singleton();
- }
- }
- }
- return instance;
- }
- }
4、静态内部类
除了上面的三种方式,还有另外一种实现单例的方式,通过静态内部类来实现。首先看一下它的实现代码:
- public class Singleton{
- private static class SingletonHolder{
- public static Singleton instance = new Singleton();
- }
- private Singleton(){}
- public static Singleton newInstance(){
- return SingletonHolder.instance;
- }
- }
这种方式同样利用了类加载机制来保证只创建一个instance实例。它与饿汉模式一样,也是利用了类加载机制,因此不存在多线程并发的问题。不一样的是,它是在内部类里面去创建对象实例。这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。也就是说这种方式可以同时保证延迟加载和线程安全。
5、枚举
再来看本文要介绍的最后一种实现方式:枚举。
- public enum Singleton{
- instance;
- public void whateverMethod(){}
- }
上面提到的四种实现单例的方式都有共同的缺点:
1)需要额外的工作来实现序列化,否则每次反序列化一个序列化的对象时都会创建一个新的实例。
2)可以使用反射强行调用私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常)。
而枚举类很好的解决了这两个问题,使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,《Effective Java》作者推荐使用的方法。不过,在实际工作中,很少看见有人这么写。
总结
本文总结了五种Java中实现单例的方法,其中前两种都不够完美,双重校验锁和静态内部类的方式可以解决大部分问题,平时工作中使用的最多的也是这两种方式。枚举方式虽然很完美的解决了各种问题,但是这种写法多少让人感觉有些生疏。个人的建议是,在没有特殊需求的情况下,使用第三种和第四种方式实现单例模式。
参考文章:http://www.jfox.info/java-dan-li-mo-shi-de-ji-zhong-xie-fa
http://devbean.blog.51cto.com/448512/203501/
我的同类文章
- •深入理解Java的ThreadLocal2016-07-18阅读115
- •Java自旋锁2016-05-05阅读336
- •谈一谈Java中的Error和Exception2015-07-29阅读757
- •Java的Atomic类分析2016-05-08阅读481
- •Java动态代理2016-04-07阅读254
参考知识库
核心技术类目
- 我的github
https://github.com/viclee2014
- 文章分类
- 阅读排行
- shell脚本报错:"[: =: unary operator expected"(65897)
- EditText限制输入字符类型的几种方式(28553)
- C++中异常类的使用方法(19465)
- QT中QPainterPath类的功能和使用方法(12218)
- Fragment中调用getActivity为null的问题(10867)
- 无法删除mysql的数据库的解决办法(10735)
- android的EditText字数检测和限制(10415)
- QT中添加Q_OBJECT出现的问题(9620)
- main函数中两个参数的用法(转)(9289)
- AsyncTask的缺陷和问题(9238)
- 评论排行
- 最新评论
-
Android应用内多进程分析和研究
FutureTask: 好文,谢谢分享!
-
Android弹幕效果实现
我是路人甲1: 哥们有没有直播类的项目分享
-
Android弹幕效果实现
viclee108: @qq_36303907:这里为了演示方便做了自动产生弹幕,自己发弹幕只需要调整一下代码就可以了
-
Android弹幕效果实现
qq_36303907: 能不能自己发弹幕?
-
自定义垂直滚动切换TextView
viclee108: @KSWistron:谢谢你的关注,我会抽空优化一下
-
自定义垂直滚动切换TextView
晚风清扬: 您好,你这个控件bug颇多,不能二次设置,不能异步设置,不然高度计算就有问题
-
android4.4上通知媒体文件更新的方式
silence__star: 还是会报错。 has leaked ServiceConnection
-
Android Studio不能获取远程依赖包的解决方法
viclee108: @anelka1985:不客气
-
Android Studio不能获取远程依赖包的解决方法
Nico: 解决了我的问题,谢啦
-
自定义垂直滚动切换TextView
kunggea2014: 感谢楼主的lib,我遇到了些问题,在listview的header上总是不能显示,不知道为什么,可以...
相关推荐
a set of instances that are associated with a single label (i.e., single-label classi- fication). However, many applications, such as gene functional prediction and text categorization, may allow the ...
Zepto-Lazyload是基于轻量级JavaScript库Zepto的一个插件,它实现了延迟加载功能,特别适合用于单页应用(SPA,Single-Page Application)。Zepto本身是对jQuery的一个精简版本,针对移动设备做了优化,而Zepto-Lazy...
开发者需要了解如何使用代码分割(code splitting)、懒加载(lazy loading)、和服务工作线程(service workers)等技术来提高应用的加载速度和响应速度。 7. **安全性**:安全性在Web开发中始终是一个重要话题,...
在压缩包文件“hibernate_lazy_for_single_end”中,可能包含了一些示例代码或测试用例,帮助读者理解并实践Hibernate的单端关联懒加载策略。这些文件可能包括实体类、映射文件、测试类等,通过实际操作展示如何配置...
13. **懒加载(Lazy Loading)**: 为了提高性能,Hibernate允许延迟加载关联的对象,直到真正需要时才从数据库中获取。 14. **事件监听器(Event Listeners)**: Hibernate允许注册监听器来处理特定的持久化事件,...
单页Web应用程序(Single Page Web Application,简称SPA)是一种特殊的Web应用程序,它提供与桌面应用程序相似的用户体验,通过只加载一个HTML页面并在用户与应用程序交互时动态更新此页面内容来实现。SPA能够与...
Jetpack懒惰图像 通过在访问者向下滚动屏幕时而不是... * on single posts, pages, or attachments by short-circuiting lazy images when * is_singular() returns false. * * See: https://github.com/Automattic/
单页应用(Single-Page Application,SPA)是现代Web开发中的一个重要概念,它与传统的多页面网站形成鲜明对比。在SPA中,整个应用程序通常只加载一个HTML页面,然后通过JavaScript动态更新页面内容,无需每次都完全...
这可能涉及到懒加载(lazy loading)、预加载(prefetching)策略,以及对首屏内容的优化,确保页面首次加载速度快。在这些测试中,可能会涉及这些优化技术的应用。 总的来说,这个"single-page-sites"项目提供了一...
在上面的代码中,我们使用了synchronized关键字来保证线程安全,并且使用lazy loading机制来实例化对象。 注册式单例 注册式单例是一种可以继承的单例模式实现方式。其特点是:可以继承注册式单例类,并且可以在...
**Lazy Loading**是一种延迟加载策略,即在真正需要数据时才进行加载。这种策略能够显著减少应用程序启动时的内存占用,并提高系统的响应速度。然而,这也意味着如果Session在加载懒加载属性之前已经关闭,则会导致`...
Just a simple static blog Generator with single static pages. 现在开始就在GithubPages上撰写吧 想法:thinking_face: 之前逛v2社区的时候看到有人写了一个利用github api的博客,顿时非常心动,但项目已经久未...
AngularJS 懒加载(Lazy Loading)是一种优化前端应用性能的技术,主要应用于单页应用程序(SPA,Single Page Application)。在传统的 AngularJS 应用中,所有模块和控制器都会在初始加载时一并加载到浏览器中,这...
除了上述两种方式,还有其他实现单例模式的方法,如`Lazy<T>`类(自.NET Framework 4.0起)提供的延迟初始化单例,以及使用`密封`(`sealed`)和静态构造函数的双检锁(Double-Check Locking)实现。每种方法都有其...
HTML5单页应用(Single-Page Application,SPA)是一种现代Web开发模式,它利用HTML5、CSS3和JavaScript等技术,构建用户仅需一次加载就能访问的网页,提供类似桌面应用的流畅体验。SPA通过动态地在后台更新内容,而...
Hibernate支持单表继承(Single Table Inheritance)、联合继承( Joined Table Inheritance)和表格分区继承(Table Per Concrete Class)。例如,使用`@Inheritance(strategy=InheritanceType.SINGLE_TABLE)`实现...
除了上述两种实现方式,还有其他方法,比如使用`Lazy<T>`类或者.NET框架提供的`System.Threading.Concurrent`命名空间中的`ConcurrentDictionary`。这些方法都提供了线程安全的单例实现,但具体选择哪种方式取决于...
单例模式通常有两种常见的实现方式:饿汉式(Eager Initialization)和懒汉式(Lazy Initialization)。 1. **饿汉式**: 饿汉式单例的特点是类加载时立即初始化,因此也称为预加载单例。这种方式简单且线程安全,...
It means that instead of having to understand every single line of code in great detail, you instead trust that other programmers, all building their respective components, have completed their ...