java中全面的单例模式多种实现方式总结
单例模式的思想
想整理一些 java 并发相关的知识,不知道从哪开始,想起了单例模式中要考虑的线程安全,就从单例模式开始吧。
以前写过单例模式,这里再重新汇总补充整理一下,单例模式的多种实现。
单例模式那件小事,看了你不会后悔
单例模式不是一件小事,快回来看看
之前在第一篇文章说,单例模式的主要思想是:
- 将构造方法私有化( 声明为 private ),这样外界不能随意 new 出新的实例对象;
- 声明一个私有的静态的实例对象,供外界使用;
- 提供一个公开的方法,让外界获得该类的实例对象
这种说法看上去没错,但也好像不太准确。其实,就算外界能随意 new 出新的实例对象,但只要我们保证我们每次使用的对象是唯一的,就可以。
单例模式的 N 种实现方式
饿汉式(线程安全,可用)
public class Singleton {
private Singleton() {
}
private static Singleton sSingleton = new Singleton();
public static Singleton getInstance() {
return sSingleton;
}
}
- 缺点: 类一加载的时候,就实例化,提前占用了系统资源。
常量式(线程安全,可用)
public class Singleton {
private Singleton() {
}
public static final Singleton sSingleton = new Singleton();
}
将实例对象用 public static final 修饰,不提供公开方法获取实例,直接通过 Singleton.sSingleton 获取。
- 缺点:与饿汉式一样,类一加载的时候,就实例化,提前占用了系统资源。
懒汉式(线程不安全,并发场景不可用)
public class Singleton {
private Singleton() {
}
private static Singleton sSingleton;
public static Singleton getInstance() {
if (sSingleton == null) {
sSingleton = new Singleton();
}
return sSingleton;
}
}
- 缺点:第一次第一次加载时反应稍慢,线程不安全。
同步的懒汉式?(线程安全,可用,不建议使用)
public class Singleton {
private Singleton() {
}
private static Singleton sSingleton;
public synchronized static Singleton getInstance() {
if (sSingleton == null) {
sSingleton = new Singleton();
}
return sSingleton;
}
}
- 缺点:第一次加载时反应稍慢,每次调用
getInstance都进行同步,造成不必要的同步开销,这种模式一般不建议使用。
双重检查锁 DCL (线程安全,大多数场景满足需求,推荐使用)
public class Singleton {
private Singleton() {
}
/**
* volatile is since JDK5
*/
private static volatile Singleton sSingleton;
public static Singleton getInstance() {
if (sSingleton == null) {
synchronized (Singleton.class) {
// 未初始化,则初始instance变量
if (sSingleton == null) {
sSingleton = new Singleton();
}
}
}
return sSingleton;
}
}
sSingleton = new Singleton() 不是一个原子操作。(XXX)故须加 volatile 关键字修饰,该关键字在 jdk1.5 之后版本才有。
- 优点:资源利用率高,第一次执行getInstance时单例对象才会被实例化,效率高。
- 缺点:第一次加载时反应稍慢,也由于Java内存模型的原因偶尔会失败。在高并发环境下也有一定的缺陷,虽然发生的概率很小。DCL模式是使用最多的单例实现方式,它能够在需要时才实例化单例对象,并且能够在绝大多数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于jdk1.6版本下使用,否则这种方式一般能够满足需求。
静态内部类(线程安全,推荐使用)
public class Singleton {
private Singleton () {
}
private static class InnerClassSingleton {
private final static Singleton sSingleton = new Singleton();
}
public static Singleton getInstance() {
return InnerClassSingleton.sSingleton;
}
}
优点:推荐使用。
枚举单例(线程安全,不建议使用)
public enum Singleton{
INSTANCE;
// 其它方法
public void doSomething(){
...
}
}
- 优点:枚举实现单例很简单,也很安全。
- 缺点:经验丰富的 Android 开发人员都会尽量避免使用枚举。官方文档有说明:相比于静态常量Enum会花费两倍以上的内存。
另类实现——利用容器实现单例
import java.util.HashMap;
import java.util.Map;
public class Singleton {
private static Map<String, Object> objMap = new HashMap<String, Object>();
private Singleton() {
}
public static void registerService(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
public static Object getService(String key) {
return objMap.get(key);
}
}
利用了 HashMap 容器 key 不可重复的特性。
- 优点:这种实现方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一接口进行获取操作,降低用户使用成本,也对用户隐藏了具体实现,降低耦合度。
- 缺点:没有私有化构造方法,用户可以 new 出新的实例对象。
防止反射破坏单例
前面的多种实现方法中,很多我们按照构造方法私有化的思想来实现的,我们知道,利用反射,仍然可以创建出新对象,这样在反射场景中,这种思想实现的单例模式就失效了,那么如何防止反射破坏单例模式呢?原理上就是在存在一个实例的情况下,再次调用构造方法时,抛出异常。下面以静态内部类的单例模式为例:
public class Singleton {
private static boolean flag = false;
private Singleton(){
synchronized(Singleton.class)
{
if(flag == false)
{
flag = !flag;
}
else
{
throw new RuntimeException("单例模式被侵犯!");
}
}
}
private static class InnerClassSingleton {
private final static Singleton sSingleton = new Singleton();
}
public static Singleton getInstance() {
return InnerClassSingleton.sSingleton;
}
}
具体测试代码,见 单例模式不是一件小事,快回来看看
防止序列化和反序列化破坏单例
通过序列化可以讲一个对象实例写入到磁盘中,通过反序列化再读取回来的时候,即便构造方法是私有的,也依然可以通过特殊的途径,创建出一个新的实例,相当于调用了该类的构造函数。要避免这个问题,我们需要在代码中加入如下方法,让其在反序列化过程中执行 readResolve 方法时返回 sSingleton 对象。
private Object readResolve() throws ObjectStreamException {
return sSingleton;
}
结语
有没有一种方式实现的单例模式在任何情况下都是一个单例呢?
——
有。就是上面说的枚举单例。枚举,就能保证在任何情况下都是单例的,并且是线程安全的。
java中全面的单例模式多种实现方式总结的更多相关文章
- Java中HashMap遍历的两种方式
Java中HashMap遍历的两种方式 转]Java中HashMap遍历的两种方式原文地址: http://www.javaweb.cc/language/java/032291.shtml 第一种: ...
- JAVA中集合输出的四种方式
在JAVA中Collection输出有四种方式,分别如下: 一) Iterator输出. 该方式适用于Collection的所有子类. public class Hello { public stat ...
- java中数组复制的两种方式
在java中数组复制有两种方式: 一:System.arraycopy(原数组,开始copy的下标,存放copy内容的数组,开始存放的下标,需要copy的长度); 这个方法需要先创建一个空的存放cop ...
- JAVA中的四种JSON解析方式详解
JAVA中的四种JSON解析方式详解 我们在日常开发中少不了和JSON数据打交道,那么我们来看看JAVA中常用的JSON解析方式. 1.JSON官方 脱离框架使用 2.GSON 3.FastJSON ...
- Java 中UDP原理机制及实现方式介绍(建议阅读者阅读前了解下Java的基础知识,一方便理解)
1.基本概念介绍: 首先得简单介绍下UDP. UDP( User Datagram Protocol )协议是用户数据报,在网络中它与TCP协议一样用于处理数据包.在OSI模型中,在第四层——传输层, ...
- Java中String对象两种赋值方式的区别
本文修改于:https://www.zhihu.com/question/29884421/answer/113785601 前言:在java中,String有两种赋值方式,第一种是通过“字面量”赋值 ...
- Java中创建线程的三种方式以及区别
在java中如果要创建线程的话,一般有3种方法: 继承Thread类: 实现Runnable接口: 使用Callable和Future创建线程. 1. 继承Thread类 继承Thread类的话,必须 ...
- Java中实现多线程的两种方式之间的区别
Java提供了线程类Thread来创建多线程的程序.其实,创建线程与创建普通的类的对象的操作是一样的,而线程就是Thread类或其子类的实例对象.每个Thread对象描述了一个单独的线程.要产生一个线 ...
- 细说java中Map的两种迭代方式
曾经对java中迭代方式总是迷迷糊糊的,今天总算弄懂了.特意的总结了一下.基本是算是理解透彻了. 1.再说Map之前先说下Iterator: Iterator主要用于遍历(即迭代訪问)Collecti ...
随机推荐
- iOS 设置字体 自定义字体
博文转载至 http://blog.csdn.net/trandy/article/details/8364517 1.网上搜索字体文件(后缀名为.ttf,或.odf) 2.把字体库导入到工程的res ...
- phpredis 中文手册和redis 教程
phpredis 中文手册 : http://www.cnblogs.com/zcy_soft/archive/2012/09/21/2697006.html 手册: http://www.cn ...
- PMP十大知识领域整理
2018-7-28至2018-12-8历时4个多月,学写了PMP(拍马屁),感觉自己经历了,哇-唉-哦-嗯这四个阶段 刚开始觉得如遇圣经,被PMP的知识体系和老师的精彩课程深深震撼! 后来觉得很多东西 ...
- 帧动画和骨骼json、极速、二进制对比
对比总结: 1. 帧动画的效率最高,但是图片超过一定帧数,资源图片非常大.比较适合帧数少,大量动画存在,要求效率高的场合. 骨骼json效率较低,已经不推荐使用. 骨骼极速,不支持网格等. 骨骼二进制 ...
- mac java环境
1.java运行环境jre:http://www.java.com/zh_CN/ 2.jdk:http://www.oracle.com/technetwork/java/javase/downloa ...
- Animate a custom Dialog,自定义Dialog动画
Inside res/style.xml <style name="AppTheme" parent="android:Theme.Light" /> ...
- 安装TortoiseSVN客户端时遇到的异常
环境:WindowsXP,安装 双击SVN安装程序"TortoiseSVN-1.8.5.25224-win32-svn-1.8.8.msi"后,出现 "无法通过Sindo ...
- 离线微博工具Open Live Writer(Windows Live Writer)安装过程及server error 500错误解决
必备条件: .net framework 3.5框架(大概是要求3.5或以上,不确定,好像没有人遇到和这个相关的问题) 2017年7月27日最新官方版0.6.2英文离线客户端网盘下载(官网的安装包无法 ...
- 170411、java Socket通信的简单例子(UDP)
服务端代码: package com.bobohe.socket; import java.io.*; import java.net.*; class UDPServer { public stat ...
- java发展历程、常用dos命令与jDK工具使用
Java菜鸟学习之旅 1.勤敲代码 2.必须将课堂上讲的内容学到位 2.1 学到会用 2.2 在学会之前不要自作主张 2.3 当天课程当天完成 3.能说会道 3.1 善于表达 3.2 学会的都能讲 4 ...