单例模式,最简单的理解是对象实例只有孤单的一份,不会重复创建实例。
这个模式已经很经典了,经典得我不再赘述理论,只给简单注释,毕竟教科书详尽太多。
解决 sonar RSPEC-2168 异味的时候,发现目前业界推荐的单例模式和教科书上的已经有了较大差异,双重锁定不再推荐,甚至业内认为的最优方案不在sonar的推荐里
于是提笔记录,顺带补充了自己对多线程单例的理解 。
这个部分没有改动,简单而经典,大致源码如下
public final class SignUtil {
/**
* 需要保持单例的对象
*/
private static Object object;
/**
* 只允许SignUtil.getInstance获取对象,也就是入口唯一
*/
private SignUtil() {
}
/**
* 对象的唯一出口 调用时才初始化(懒加载)
* @return Object 确保单线程情况下这里出去就是初始化好的
*/
public static Object getInstance() {
if (null == object) {
object = new Object();
}
return object;
}
/**
* 内部函数也必须使用 getInstance这个入口
*/
public static String getString() {
return getInstance().toString();
}
}
public final class SignUtil {
/**
* 需要保持单例的对象
* 这里需要声明对象是易失的,因为object = new Object()不是一个原子操作,是被分拆为了实例化和初始化,一个申请空间,一个分配值
* 那么就有可能出现 C在第三瞬间进入getInstance函数,发现null!=object,此时对象实例化了但没初始化就直接返回,是个高危操作
*/
private volatile static Object object;
/**
* 只允许SignUtil.getInstance获取对象,也就是入口唯一
*/
private SignUtil() {
}
/**
* 对象的唯一出口
*
* @return Object 多线程情况下这里出去就是初始化好的
*/
public static Object getInstance() {
// 第0瞬间 A B 两个线程同时初始化,一看都是null嘛
if (null == object) {
// 第1瞬间 A B都进来了,因为不能重复初始化,所以被synchronized锁约束开始竞争.
// A 赢了SignUtil的对象锁,B 只能等着
synchronized (SignUtil.class) {
// 这里为什么不直接object = new Object()呢?
// 因为B还等着呢,直接初始化就拦不住B再来一次初始化了.
if (null == object) {
// 第2瞬间, A终于初始化成功,且B不会重新初始化了.
object = new Object();
// 第3瞬间,因为object被volatile约束了,可以视为原子操作,补上最后一个漏洞,成功返回。
}
}
}
return object;
}
/**
* 内部函数也必须使用 getInstance这个入口
*/
public static String getString() {
return getInstance().toString();
}
}
可以看到,三的要点太多了,很经典的双重锁定,但是不够简单优雅。目前更推荐下面两种格式
JDK8 带来的一个特性之一即是synchronized关键字,从原来的monitor重量级锁,转变成了由偏向锁进行逐级升级到重量级锁。换句话说,使用synchronized的代价被降低了,我们可以将上面的函数进行一个改进,让它保持简单和优雅。
但是代价依旧存在,以下适合并发冲突不严重的项目。
public final class SignUtil {
/**
* 需要保持单例的对象
*/
private static Object object;
/**
* 只允许SignUtil.getInstance获取对象,也就是入口唯一
*/
private SignUtil() {
}
/**
* 对象的唯一出口 是的,仅比单线程版多了一个synchronized
* @return Object 由于synchronized,同一瞬间只能有一个对象进行获取实例
*/
public static synchronized Object getInstance() {
if (null == object) {
object = new Object();
}
return object;
}
/**
* 内部函数也必须使用 getInstance这个入口
*/
public static String getString() {
return getInstance().toString();
}
}
很巧妙地利用了jvm的类加载机制。那就是静态内部类的延迟加载性完成单例。
public final class SignUtil {
/**
* 利用jvm的初始化规则 静态内部类的静态内部对象,只有在调用时才对静态类开始初始化,
* 类的初始化过程是线程安全的,所以也只有一个线程能进行初始化
*/
private static class Node {
/**
* 在读写调用时才真正初始化,也就是懒加载
*/
private static final Object object = new Object();
}
/**
* 只允许SignUtil.getInstance获取对象,也就是入口唯一
*/
private SignUtil() {
}
/**
* 不再是对象的唯一出口,其他地方也只要读写都能完成初始化
*
* @return Object 调用时,会触发内部静态类的初始化,返回时,初始化已完成
*/
public static Object getInstance() {
return Node.object;
}
/**
* 内部函数终于不用再依赖 getInstance这个入口
*/
public static String getString() {
return Node.object.toString();
}
}
听起来很魔鬼,但实际上,上述的多线程程单例都有两个共同的缺陷可以做到:a 反射Constructor::setAccessible将私有构造函数改为公有函数 b.序列化时还是会返回多个实例。
解决方法为改造构造函数和申明readResolve函数,参考如下,解决方案是通用的。
public final class SignUtil {
private static volatile boolean init = false;
private static class Node {
private static final Object object = new Object();
}
/**
* 添加一个volatile的变量去判断,防止反射初始化
* 第二次初始化会抛出类强制转换异常 当然你也可以用其他运行时异常
*/
private SignUtil() {
if (!init) {
init = true;
} else {
throw new ClassCastException();
}
}
public static Object getInstance() {
return Node.object;
}
public static String getString() {
return Node.object.toString();
}
/**
* 反序列化时直接返回单例的对象,这么写的原因在 ObjectInputStream::readUnshared里
*/
private Object readResolve() {
return Node.object;
}
}
和4.2一样,《Effective Java 》找到了另一种利用jvm类加载机制实现单例的方法:单元素枚举单例。
这里有几个前提:
不得不说,单元素枚举的确成功避免了重重的繁琐,但代价是没有了懒加载的特性,变成了饿汉模式
public enum SignUtil {
/**
* 从javap的反编译结果看,会变成一个类公开的静态变量,也就是饿汉模式
* public static final SignUtil INSTANCE = new SignUtil();
* 也就是会在加载类时直接初始化INSTANCE对象,而object对象是在构造时作为内部变量初始化,而构造函数是由jvm保证的
*/
INSTANCE;
/**
* 由于INSTANCE单例,所以object才是单例的
*/
private final Object object = new Object();
public Object getInstance() {
return object;
}
public String getString() {
return object.toString();
}
}
补一下javap反编译后的结果
public final class SignUtil extends java.lang.Enum<SignUtil> {
public static final SignUtil INSTANCE;
private final java.lang.Object object;
private static final SignUtil[] $VALUES;
public static SignUtil[] values();
public static SignUtil valueOf(java.lang.String);
private SignUtil(java.lang.Object);
public java.lang.Object getInstance();
public java.lang.String getString();
static {};
}
由于多元素枚举的构造函数可以被反射修改成公用函数并设置object,但由于INSTANCE和object都是final约束的,所以修改就会报错,以此保证了单例性。
所以按照理解 多元素枚举也能完成单例,只是适用场景偏少
public enum SignUtil {
/*
* 对的,唯一的区别就是由无参变成了有参构造,本质是不变的饿汉
* public static final SignUtil INSTANCE = new SignUtil(new Object());
*/
INSTANCE(new Object()),
OTHER(new Object());
private final Object object;
private SignUtil(Object object) {
this.object = object;
}
public Object getInstance() {
return this.object;
}
public String getString() {
return this.object.toString();
}
}
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende
我正在使用active_admin,我在Rails3应用程序的应用程序中有一个目录管理,其中包含模型和页面的声明。时不时地我也有一个类,当那个类有一个常量时,就像这样:classFooBAR="bar"end然后,我在每个必须在我的Rails应用程序中重新加载一些代码的请求中收到此警告:/Users/pupeno/helloworld/app/admin/billing.rb:12:warning:alreadyinitializedconstantBAR知道发生了什么以及如何避免这些警告吗? 最佳答案 在纯Ruby中:classA
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
我早就知道Ruby中的“常量”(即大写的变量名)不是真正常量。与其他编程语言一样,对对象的引用是唯一存储在变量/常量中的东西。(侧边栏:Ruby确实具有“卡住”引用对象不被修改的功能,据我所知,许多其他语言都没有提供这种功能。)所以这是我的问题:当您将一个值重新分配给常量时,您会收到如下警告:>>FOO='bar'=>"bar">>FOO='baz'(irb):2:warning:alreadyinitializedconstantFOO=>"baz"有没有办法强制Ruby抛出异常而不是打印警告?很难弄清楚为什么有时会发生重新分配。 最佳答案
给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO