Effective Java - 构造器私有、枚举和单例
Singleton 是指仅仅被实例化一次的类。Singleton代表了无状态的对象像是方法或者本质上是唯一的系统组件。使类称为Singleton 会使它的客户端测试变得十分困难。因为不可能给Singleton替换模拟实现。除非实现一个充当其类型的接口
饿汉式单例
静态常量
下面有两种方法实现一个单例,两者都基于保持构造器私有并且导出一个公有的静态成员提供一个唯访问该实例的入口。在第一种方法中,这个成员的属性是final的
// 提供属性是公有的、唯一的单例
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis();
public void leaveTheBuilding();
}
这是一个饿汉式的实现。这个私有的构造器仅仅被调用一次,因为Elvis 是 static final的,所以INSTANCE是一个常量,编译期间进行初始化,并且值只能被初始化一次,致使INSTANCE不能再指向任意其他的对象,没有任何客户端能够改变这个结果。但是需要注意一点:有特权的客户端能够使用反射中的AccessibleObject.setAccessible访问私有的构造器。为了防御这种攻击,把构造器修改为在第二次实例化的时候抛出异常。见如下的例子
public class Elvis {
static boolean flag = false;
private Elvis(){
if(flag == false) {
flag = !flag;
}
else {
throw new RuntimeException("单例模式被侵犯!");
}
}
public static class SingletonHolder {
private static final Elvis INSTANCE = new Elvis();
}
public static Elvis getInstance(){
return SingletonHolder.INSTANCE;
}
public static void main(String[] args) throws Exception {
Class<Elvis> el = Elvis.class;
// 获得无参数私有的构造器
Constructor<Elvis> constructor = el.getDeclaredConstructor();
// 暴力破解private 私有化
constructor.setAccessible(true);
// 生成新的实例
Elvis elvis = constructor.newInstance();
Elvis instance = Elvis.getInstance();
System.out.println(elvis == instance);
}
}
Exception in thread "main" java.lang.ExceptionInInitializerError
at effectiveJava.effective03.Elvis.getInstance(Elvis.java:22)
at effectiveJava.effective03.Elvis.main(Elvis.java:33)
Caused by: java.lang.RuntimeException: 单例模式被侵犯!
at effectiveJava.effective03.Elvis.<init>(Elvis.java:13)
at effectiveJava.effective03.Elvis.<init>(Elvis.java:5)
at effectiveJava.effective03.Elvis$SingletonHolder.<clinit>(Elvis.java:18)
... 2 more
注释掉利用反射获取私有构造函数的代码,发现instance实例可以正常输出
Elvis instance = Elvis.getInstance();
System.out.println(instance);
console: effectiveJava.effective03.Elvis@266474c2
在实现Singleton 的第二种方法中,公有的成员是个静态方法
public class ElvisSingleton {
private static final ElvisSingleton INSTANCE = new ElvisSingleton();
private ElvisSingleton(){}
public static ElvisSingleton newInstance(){
return INSTANCE;
}
public void leaveBuilding(){}
}
对于静态方法newInstance来说所有的调用,都会返回一个INSTANCE对象,所以,永远不会创建其他ElvisSingleton实例
公有属性最大的优势在于能够很清楚的描述类是单例的:公有的属性是final的,所以总是能够包含相同的对象引用。第二个优势就是就是比较简单。
静态代码块
静态代码块是静态常量的变种,就是把静态常量的初始化放在了静态代码块中解析,初始化。读者可能对这种方式产生疑惑,请详见
类加载机制 https://blog.csdn.net/ns_code/article/details/17881581
public class ElvisStaticBlock {
private static final ElvisStaticBlock block;
static {
block = new ElvisStaticBlock();
}
private ElvisStaticBlock(){}
public static ElvisStaticBlock newInstance(){
return block;
}
}
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
懒汉式单例
与饿汉式对应的就是懒汉式,这两者都是属于单例模式的应用,懒汉式含有一层懒加载(lazy loading)的概念,也叫做惰性初始化。
public class ElvisLazyLoading {
private static ElvisLazyLoading instance;
private ElvisLazyLoading(){}
public static ElvisLazyLoading newInstance(){
if(instance == null){
instance = new ElvisLazyLoading();
}
return instance;
}
}
初始的时候不会对INSTANCE进行初始化,它的默认值是null,在调用newInstance方法时会判断,若INSTANCE为null,则会把INSTANCE的引用指向ElvisLazyLoading的构造方法。
这种方式能够实现一个懒加载的思想,但是这种写法会存在并发问题,由于多线程各自运行自己的执行路径,当同时执行到 INSTANCE = new ElvisLazyLoading() 代码时,各自的线程都认为自己应该创建一个新的ElvisLazyLoading对象,所以最后的结果可能会存在多个ElvisLazyLoading 实例,所以这种方式不推荐使用
尝试加锁
很显然的,可以尝试对newInstance()方法加锁来避免产生并发问题,但是这种方式不可能,由synchronized加锁会导致整个方法开销太大,在遇见类似问题时,应该尝试换一种方式来解决,而不应该只通过简单粗暴的加锁来解决一切并发问题。
public synchronized static ElvisLazyLoading newInstance(){
if(INSTANCE == null){
INSTANCE = new ElvisLazyLoading();
}
return INSTANCE;
}
同步代码块
synchronized关键字不仅可以锁住方法的执行,也可以对方法中的某一块代码进行锁定,也叫做同步代码块
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
不要觉得只要加锁了,就不会存在线程安全问题,线程是Java中很重要的一个课题,需要细细研究。这种同步代码块的方式也会存在线程安全问题,当多个线程同时判断自己的singleton 实例为null的时候,同样会创建多个实例。
双重检查
Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (instance == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (instance == null),直接return实例化对象。
public class ElvisDoubleCheck {
private static volatile ElvisDoubleCheck instance;
private ElvisDoubleCheck(){}
public static ElvisDoubleCheck newInstance(){
if(instance == null){
synchronized (ElvisDoubleCheck.class){
if (instance == null){
instance = new ElvisDoubleCheck();
}
}
}
return instance;
}
}
优点:线程安全;延迟加载;效率较高。
静态内部类单例
静态内部类的单例与饿汉式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Elvis类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在ElvisStaticInnerClass类被装载时并不会立即实例化,而是在需要实例化时,调用newInstance方法,才会装载SingletonInstance类,从而完成ElvisStaticInnerClass的实例化。
public class ElvisStaticInnerClass {
private ElvisStaticInnerClass(){}
private static class SingletonInstance{
private static final ElvisStaticInnerClass instance = new ElvisStaticInnerClass();
}
public static ElvisStaticInnerClass newInstance(){
return SingletonInstance.instance;
}
}
优点:避免了线程不安全,延迟加载,效率高。
枚举单例
实现Singleton的第四种方法是声明一个包含单个元素的枚举类型
public enum ElvisEnum {
INSTANCE;
public void leaveTheBuilding(){}
}
这种方法在功能上与公有域方法相似,但更加简洁。无偿地提供了序列化机制,有效防止多次实例化,即使在面对复杂的序列化或者反射攻击的时候。单元素的枚举类型经常成为实现Singleton的最佳方法。
优点: 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
缺点:当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候。
后记:
看完本文,你是否对构造器私有、枚举和单例这个主题有了新的认知呢?
你至少应该了解:
- 单例模式的几种写法及其优缺点分析
- 为什么反射能够对私有构造器产生破坏?
- 有哪几种比较好用的线程安全的单例模式?
公众号提供 优质Java资料 以及CSDN免费下载 权限,欢迎你关注我

参考资料:
如何防止单例模式被JAVA反射攻击 https://blog.csdn.net/u013256816/article/details/50525335
《Effective Java》
Effective Java - 构造器私有、枚举和单例的更多相关文章
- 编写高质量代码改善C#程序的157个建议——建议105:使用私有构造函数强化单例
建议105:使用私有构造函数强化单例 单例指一个类型只生成一个实例对象.单例的一个简单实现如下所示: static void Main(string[] args) { Singleton.Insta ...
- Effective Java —— 用私有构造器或枚举类型强化单例属性
本文参考 本篇文章参考自<Effective Java>第三版第三条"Enforce the singleton property with a private construc ...
- 《Effective Java》 读书笔记(三) 使用私有构造方法或枚举实现单例类
1.单例类到现在为止算是比较熟悉的一种设计模式了,最开始写单例模式是在C#里面,想要自己实现一个单例类,代码如下: public class Instance { private static rea ...
- Java设计模式透析之 —— 单例(Singleton)
写软件的时候经常需要用到打印日志功能,可以帮助你调试和定位问题,项目上线后还可以帮助你分析数据.但是Java原生带有的System.out.println()方法却很少在真正的项目开发中使用,甚至像f ...
- java设计模式—单例模式(包含单例的破坏)
什么是单例模式? 保证一个了类仅有一个实例,并提供一个访问它的全局访问点. 单例模式的应用场景? 网站的计数器,一般也是采用单例模式实现,否则难以同步: Web应用的配置对象的读取,一般也应用单例模式 ...
- 《JAVA笔记 day08 静态_单例》
//static关键字: /* 静态方法的使用注意事项: 1,静态方法不能访问非静态的成员. 但是非静态是可以访问静态成员的. 说明:静态的弊端在于访问出现了局限性.好处是可以直接别类名调用. 2,静 ...
- Java Notes 00 - Singleton Pattern(单例总结)
转:http://hukai.me/java-notes-singleton-pattern/ 这里不赘述单例模式的概念了,直接演示几种不同的实现方式. 0)Eager initialization ...
- 初学者学Java设计模式(一)------单例设计模式
单例设计模式 单例设计模式是指一个类只会生成一个对象,优点是他可以确保所有对象都访问唯一实例. 具体实现代码如下: public class A { public static void main(S ...
- 【Java】【设计模式】单例设计模式
思想: 为了避免其他程序过多建立该类对象,先禁止其他程序建立该类对象 为了让其他程序可以访问到该类对象,只好在本类中自定义一个对象 为了方便其他程序对自定义对象的访问,可以对外提供一些访问方式 代码体 ...
随机推荐
- It's about trust
所有问题, 最后,它归结为:信任. 你能相信别人? 我不是Low esteem.相反,我有信心.问题是.我有一个别人缺乏信任的. Daria家长们说,伪君子:我们必须相信你. Daria马上问:然后, ...
- cocos2d-x 在XML分析和数据存储
无意中起到一周中的游戏,哎,时间过得总是打得那么快时,. .. 于是今天决定看一下之前不怎么非常熟悉的XML;(之前做游戏时数据的储存用到过XML.但这块是还有一个同事在做,所以不怎么熟悉), 看了看 ...
- Java之java.lang.IllegalMonitorStateException
今天又中彩了, 原本很简单的多线程程序, 蓦然间冒了个"java.lang.IllegalMonitorStateException" , 杀了个措手不及. 一直纳闷, 为什么为什 ...
- Linux IO模式
原文地址:https://segmentfault.com/a/1190000003063859 同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案 ...
- VS2012发布到XP平台
默认情况下,你的VS2012工程发布后,在XP下运行会出现提示“not a valid win32 application”. 微软推出了Visual Studio 2012 update 1可以支持 ...
- Qt侠:像写诗一样写代码,玩游戏一样的开心心情,还能领工资!
[软]上海-Qt侠 2017/7/12 16:11:20我完全是兴趣主导,老板不给我钱,我也要写好代码!白天干,晚上干,周一周五干,周末继续干!编程已经深入我的基因,深入我的骨髓,深入我的灵魂!当我解 ...
- 零元学Expression Blend 4 - Chapter 13 用实例了解布局容器系列-「Pathlistbox」I
原文:零元学Expression Blend 4 - Chapter 13 用实例了解布局容器系列-「Pathlistbox」I 本系列将教大家以实做案例认识Blend 4 的布局容器,此章介绍的布局 ...
- .net的数据类型说明
C#提供称为简单类型的预定义结构类型集,简单类型通过保留字标识, 而这些保留字只是System命名空间中预定义结构类型的别名. 保留字与预定义结构类型的对应如下: 保留字 预定义结构类型 sbyte ...
- 使用MinGW编译Boost
1.下载Boost(http://www.boost.org) 我目前用的是1.61.0版本 2.将MinGW下的bin目录完整路径设置到系统环境变量Path中,保证cmd命令行能找到gcc,g++等 ...
- 如何作为一个优秀的ERP实施顾问
原文地址:如何作为一个优秀的ERP实施顾问作者:天思软件 作一个优秀的ERP实施顾问无论是天思.金蝶,用友,还是其他业务软件.实施顾问的发展道路都差不多. 1.初级实施顾问,中级实施顾问,高级实施顾问 ...