文档章节

单例模式Java的七种写法

小致dad
 小致dad
发布于 2017/07/24 10:38
字数 1462
阅读 497
收藏 6

单例模式算是开发中使用比较多的设计模式,Hibernate的SessionFactory、Spring的ApplicationContext、EhCache的源代码里的CacheManager类等都是单例模式。

第一种(懒汉,线程不安全,不建议)

这种写法lazy loading很明显,但是致命的是在多线程不能正常工作。

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}  

第二种(懒汉,线程安全,不建议)

这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}  

第三种(饿汉,建议)

这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}  

第四种(饿汉,变种,不建议)

 表面上看起来差别挺大,其实更第三种方式差不多,都是在类初始化即实例化instance。

public class Singleton {  
    private static Singleton instance = null;  
    static {  
    instance = new Singleton();  
    }  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
} 

第五种(静态内部类,建议)

这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。

public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
}  

第六种(枚举,强烈建议)

这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。

上面的类Resource是我们要应用单例模式的资源,具体可以表现为网络连接,数据库连接,线程池等等。 
获取资源的方式很简单,只要 Singleton.INSTANCE.getInstance() 即可获得所要实例。

public enum Singleton03 {
    INSTANCE;
    private Resource instance;

    Singleton03() {
        instance = new Resource();
    }

    public Resource getInstance() {
        return instance;
    }

    public static class Resource {
        private Resource() {
            System.out.println("创建了Resource实例!");
        }

        public void whoAmI() {
            System.out.println(this.toString());
        }
    }
}

执行结果

第七种(双重校验锁,建议)

这个是第二种方式的升级版,俗称双重检查锁定,在JDK1.5之后,双重检查锁定才能够正常达到单例效果。

public class Singleton {
    private volatile static Singleton singleton;

    private Singleton() {
        System.out.println("创建了Singleton04实例!");
    }

    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
                return singleton;
            }
        }
        return singleton;
    }
}

总结

有两个问题需要注意:

1.如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。

2.如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。

对第一个问题修复的办法是:

private static Class getClass(String classname)      
                                         throws ClassNotFoundException {     
      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();     
      
      if(classLoader == null)     
         classLoader = Singleton.class.getClassLoader();     
      
      return (classLoader.loadClass(classname));     
   }     
}  

 对第二个问题修复的办法是:

public class Singleton implements java.io.Serializable {     
   public static Singleton INSTANCE = new Singleton();     
      
   protected Singleton() {     
        
   }     
   private Object readResolve() {     
            return INSTANCE;     
      }    
}   

对我来说,我比较喜欢第三种和第五种方式,简单易懂,而且在JVM层实现了线程安全(如果不是多个类加载器环境),一般的情况下,我会使用第三种方式,只有在要明确实现lazy loading效果时才会使用第五种方式,另外,如果涉及到反序列化创建对象时我会试着使用枚举的方式来实现单例,不过,我一直会保证我的程序是线程安全的,而且我永远不会使用第一种和第二种方式,如果有其他特殊的需求,我可能会使用第七种方式,毕竟,JDK1.5已经没有双重检查锁定的问题了。

© 著作权归作者所有

共有 人打赏支持
小致dad
粉丝 133
博文 533
码字总数 580132
作品 0
济南
技术主管
为什么我墙裂建议大家使用枚举来实现单例。

关于单例模式,我的博客中有很多文章介绍过。作为23种设计模式中最为常用的设计模式,单例模式并没有想象的那么简单。因为在设计单例的时候要考虑很多问题,比如线程安全问题、序列化对单例的...

06/10
0
0
练就Java24章真经—你所不知道的工厂方法

前言 最近一直在Java方向奋斗《终于,我还是下决心学Java后台了》,今天抽空开始学习Java的设计模式了 。计划有时间就去学习,你这么有时间,还不来一起上车吗? 之所以要学习Java模式,是因...

codeGoogle
09/04
0
0
设计模式15——Template Method设计模式

Template Method模板方法设计模式定义一个操作中算法的骨架,将具体步骤的执行延迟到子类中实现。Java中的抽象类就是使用了模板方法设计模式。模板方法设计模式结构如下: 以文档处理为例,T...

小米米儿小
2014/01/24
0
0
Java设计模式之工厂模式(简单工厂模式,工厂方法模式,抽象工厂模式)

工厂模式出现的原因 在java中,创建一个对象最简单的方法就是使用new关键字。但在一些复杂的业务逻辑中,创建一个对象不只需要new一行代码就成了,可能需要一些列的初始化设置,或先创建一些...

渡劫锦官城
08/21
0
0
设计模式 2014-12-19

book: 阎宏《JAVA与模式》 架构设计栏目 http://blog.csdn.net/enterprise/column.html 概要: http://bbs.csdn.net/forums/Embeddeddriver 23种设计模式分别是: 1.单例模式 2.工厂方法模式...

jayronwang
2014/12/19
0
0

没有更多内容

加载失败,请刷新页面

加载更多

presto Resource Groups

资源组 资源组限制资源使用,可以对运行在其中的任务执行强制排队策略,也可以在子组间分配资源。一个任务属于一个资源组,消费这个组的资源。除了对排队任务限制外,当资源组用完资源时,不...

张欢19933
6分钟前
0
0
python连接Mysql错误:_mysql_exceptions: (1054

错误:python连接Mysql错误:_mysql_exceptions.OperationalError: (1054, "Unknown column 'CVE' in 'field lis解决办法 参考: https://blog.csdn.net/Homewm/article/details/81663504......

fang_faye
16分钟前
0
0
三分钟构建高性能WebSocket服务 | 超优雅的Springboot整合Netty方案

前言 每当使用SpringBoot进行Weboscket开发时,最容易想到的就是spring-boot-starter-websocket(或spring-websocket)。它可以让我们使用注解,很简单的进行Websocket开发,让我们更多的关注...

Yeauty
22分钟前
5
1
Windows下:Eclipse通过java开发spark程序【1】

准备:本机环境设置环境 jdk1.8,hadoop2.8.1(与服务器上hadoop环境保持一致) 第一步: 需要下载windows版本 bin目录下的文件,替换hadoop目录下原来的bin目录下的文件。下载网址是: https:...

那年的暖风
24分钟前
0
0
并发编程之AQS

一、定义 AQS(AbstractQueuedSynchronizer抽象类):是一个用于构建锁和同步容器的框架(通过继承AQS并实现它的抽象方法),它不是通过synchronized给对象加锁实现的,而仅仅只是一个工具类...

狠一点
26分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部