全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

详解Java中如何正确书写单例模式

单例模式算是设计模式中最容易理解,也是最容易手写代码的模式,但是其中涉及的知识点却一点也不少,所以经常作为面试题来考。一般单例都是五种写法:懒汉,饿汉,双重校验锁,静态内部类和枚举。为了记录学习过程的过程,这里整理了几种常见的单例写法,

青铜5:(Lazy-loaded,但线程不安全)

当被问到要实现一个单例模式时,很多人的第一反应是写出如下的代码,包括教科书上也是这样教我们的。

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

这段代码简单明了,而且使用了延迟加载模式,但是线程不安全。多线程环境下调用 getInstance() 方法,可能会发生多个线程进入if语句的程序代码块。

懒汉式:synchronized(Lazy-loaded,线程安全,但不高效)

为了解决上面的问题,最简单的方法是将整个 getInstance() 方法设为同步(synchronized)。

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

虽然做到了线程安全、延迟加载,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance() 方法。但是synchronized操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。这种模式导致即使在单例创建完成后,每次依然只有一个线程可以访问getInstance()方法,会导致潜在的性能问题。这就引出了双重检验锁。

饿汉式:static final field(非Lazy-loaded)

这种方法非常简单,因为单例的实例被声明成 static final,在第一次加载类到内存中时就会初始化,所以创建实例对象是线程安全的(由JVM实现保证)。

public class Singleton{
 //类加载时就初始化
 private static final Singleton instance = new Singleton();
 private Singleton(){}
 public static Singleton getInstance(){  // Singleton with static factory
 return instance;
 }
}

它不是一种懒加载模式,instance会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。这会导致一些使用限制:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。类似的方法还有:

public class Singleton{
 public static final Singleton instance = new Singleton(); // Singleton with public final field
 private Singleton(){}
}
// <Effective Java>第14页讲诉了两者的差异

双重检验锁 + volatile(Lazyload,线程安全,但晦涩)

双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法。程序员称其为双重检查锁,因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例对象了。

public static Singleton getSingleton() {
 if (instance == null) {       //Single Checked
  synchronized (Singleton.class) {
   if (instance == null) {     //Double Checked
    instance = new Singleton();
   }
  }
 }
 return instance ;
}

这段代码看起来很完美,很可惜它是有问题的。主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。

  1. 给 instance 分配内存
  2. 调用 Singleton 的构造函数来初始化成员变量
  3. 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

但是在 JVM 的JIT编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。为此,我们需要将 instance 变量声明成 volatile 。

public class Singleton {
 private volatile static Singleton instance; //声明为 volatile
 private Singleton(){}
 public static Singleton getSingleton() {
  if (instance == null) {       
   synchronized (Singleton.class) {
    if (instance == null) {  
     instance = new Singleton();
    }
   }
  }
  return instance;
 } 
}

但是特别注意在 Java 1.5 以前的版本使用了 volatile 的双检锁还是有问题的,这个问题在 Java 1.5 中才得以修复,所以在这之后才可以放心使用 volatile。

静态内部类:IoDH,initialization-on-demand holder

这个模式综合使用了Java的静态内部类和多线程缺省同步锁的知识,很巧妙地同时实现了延迟加载和线程安全。

public class Singleton {
 private Singleton() {}
 private static class LazyHolder {
  private static final Singleton INSTANCE = new Singleton();
 }
 public static Singleton getInstance() {   // From wikipedia
  return LazyHolder.INSTANCE;
 }
}

静态内部类相当于其外部类的static部分,它的对象不依赖于外部类对象,因此可以直接创建。静态内部类只有在第一次被使用的时候才会被转载。

多线程缺省同步锁

 大家都知道,在多线程开发中,为了解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制。但是在某些情况中,JVM已经隐含地为您执行了同步,这些情况下就不需要手动进行同步控制了。这些情况包括

   1.由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时

 2.访问final字段时

 3.在创建线程之前创建对象时

 4.线程可以看见它将要处理的对象时

枚举 Enum

从Java 1.5起,只需编写一个包含单个元素的枚举类型:

public enum Singleton {
 INSTANCE;
}

这种方法在功能上与公有域方法相近,但是它更加简洁,无偿地提供了序列化机制,绝对防止多次实例化,即使是在面向复杂的序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型以及成为实现Singleton的最佳方法。

--------------------以下是几个细节存疑的实现方法---------------------

1.static final到底有哪些细节

2.static field处的赋值初始化到底和static代码块有先后吗?

3.静态内部类的单例模式到底怎么写

4.P50 in Java EE设计模式解析与应用中的例子真的有Lazyload效果吗?

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!


# Java  # 单例模式  # java 单例的五种实现方式及其性能分析  # Java单例模式简单示例  # Java多线程中的单例模式两种实现方式  # java 单例模式和工厂模式实例详解  # JAVA多线程并发下的单例模式应用  # Java实现单例模式之饿汉式、懒汉式、枚举式  # JAVA 静态的单例的实例详解  # 加载  # 是在  # 多个  # 多线程  # 就会  # 使用了  # 是一种  # 会有  # 是有  # 只需  # 这段  # 这种方法  # 最容易  # 不安全  # 都是  # 是一个  # 几个  # 序列化  # 还没有  # 大家都 


相关文章: 如何做静态网页,sublimetext3.0制作静态网页?  实例解析Array和String方法  如何彻底删除建站之星生成的Banner?  上海网站制作开发公司,上海买房比较好的网站有哪些?  Swift开发中switch语句值绑定模式  网站制作知乎推荐,想做自己的网站用什么工具比较好?  定制建站是什么?如何实现个性化需求?  微信小程序制作网站有哪些,微信小程序需要做网站吗?  如何在腾讯云服务器上快速搭建个人网站?  建站之星手机一键生成:多端自适应+小程序开发快速建站指南  如何在建站宝盒中设置产品搜索功能?  深圳网站制作设计招聘,关于服装设计的流行趋势,哪里的资料比较全面?  公司网站设计制作厂家,怎么创建自己的一个网站?  湖南网站制作公司,湖南上善若水科技有限公司做什么的?  魔方云NAT建站如何实现端口转发?  公司网站的制作公司,企业网站制作基本流程有哪些?  如何用景安虚拟主机手机版绑定域名建站?  常州自助建站工具推荐:低成本搭建与模板选择技巧  黑客如何通过漏洞一步步攻陷网站服务器?  阿里云高弹*务器配置方案|支持分布式架构与多节点部署  如何快速配置高效服务器建站软件?  如何通过wdcp面板快速创建网站?  电商平台网站制作流程,电商网站如何制作?  相册网站制作软件,图片上的网址怎么复制?  无锡制作网站公司有哪些,无锡优八网络科技有限公司介绍?  巅云智能建站系统:可视化拖拽+多端适配+免费模板一键生成  教学论文网站制作软件有哪些,写论文用什么软件 ?  建站之星如何快速更换网站模板?  公司网站建设制作费用,想建设一个属于自己的企业网站,该如何去做?  SQL查询语句优化的实用方法总结  自助网站制作软件,个人如何自助建网站?  如何在Windows虚拟主机上快速搭建网站?  网站专业制作公司,网站编辑是做什么的?好做吗?工作前景如何?  如何在万网自助建站平台快速创建网站?  网站建设制作需要多少钱费用,自己做一个网站要多少钱,模板一般多少钱?  如何选择可靠的免备案建站服务器?  建站org新手必看:2024最新搭建流程与模板选择技巧  ,怎么用自己头像做动态表情包?  山东云建站价格为何差异显著?  如何快速搭建高效可靠的建站解决方案?  如何用PHP快速搭建高效网站?分步指南  建站之星如何实现PC+手机+微信网站五合一建站?  头像制作网站在线制作软件,dw网页背景图像怎么设置?  大连网站制作费用,大连新青年网站,五年四班里的视频怎样下载啊?  C++如何使用std::optional?(处理可选值)  厦门模型网站设计制作公司,厦门航空飞机模型掉色怎么办?  如何快速选择适合个人网站的云服务器配置?  教学网站制作软件,学习*后期制作的网站有哪些?  如何用已有域名快速搭建网站?  如何高效搭建专业期货交易平台网站? 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。