Java单例模式知识点详解
2017-03-15 22:04
405 查看
Java 单例模式概念
单例模式:确保在java程序中,一个类class只有一个实例,并自行实例化,向整个程序提供这个实例。好处:
1. 这样可以节省内存,限制了实例的个数,利于回收;
2. 保证了资源类的同步操作,避免了并发问题。
Java 单例模式的写法
常见的单例模式写法有:懒汉式,饿汉式,双重校验锁,静态内部类,枚举。懒汉式,线程不安全
/** * 单例模式——懒汉式[线程不安全] * Created by jinzifu on 2017/3/26. */ public class Singleton { private static Singleton instance; private Singleton() { } public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
此种写法,当多线程的时候, if (instance == null) {…}会出现并发问题,进而产生多个实例(就不是单例了)。
改进版——懒汉式,线程安全
/** * 单例模式——懒汉式[线程安全] * Created by jinzifu on 2017/3/26. */ public class Singleton { private static Singleton instance; private Singleton() { } public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
这里对getInstance方法加了synchronized同步锁。避免了多线程时因并发而产生多个实例的问题。
但是此种做法,无论是否已实例化都做同步锁使线程挂起,效率很低。可以仅针对需要实例化的情况进行同步加锁。
再次改进版——双重校验锁
/** * 单例模式——双重校验锁 * Created by jinzifu on 2017/3/26. */ public class Singleton { private static Singleton instance; private Singleton() { } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
这里仅仅对 if (instance == null) {}的情况以加锁块的形式做同步。并且针对多线程时,并发进入 if (instance == null) {}内的情况做优化,增加了一步非空判断。这也就是双重校验锁单例模式。
这里,既对多线程情况做了优化,又保证了效率。这样就真的没问题了吗?不见得吧~
这里引入java中的指令重排优化概念~
指令重排优化指的是在不改变原语义的情况下,通过调整指令的执行顺序让程序运行的更快。Java的JVM可以自由的进行指令重排优化处理。
在JVM创建新的对象时,主要要经过三步:
1. 分配内存
2. 初始化构造器
3. 将对象指向分配的内存的地址
由于有指令重排优化机制的存在,所以第二步和第三步的顺序是可以改变的。也就是会出现分配了指向分配内存的地址却还没有初始化构造器的情况。这时候会得到一个初始化失败的实例,若紧接着调用getInstance方法,返回的将是一个不正确的对象实例,程序就会报错。
这就是双重校验锁会失效的原因。不过还好在JDK1.5及之后版本增加了volatile关键字。volatile的一个语义是禁止指令重排序优化,也就保证了instance变量被赋值的时候对象已经是初始化过的,从而避免了上面说到的问题。如下:
/** * 单例模式——双重校验锁(volatile) * Created by jinzifu on 2017/3/26. */ public class Singleton { private static volatile Singleton instance; private Singleton() { } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
这样的再次优化,就彻底解决双重校验锁带来的问题了。
饿汉式,线程安全
/** * 单例模式——饿汉式(线程安全) * Created by jinzifu on 2017/3/26. */ public class Singleton { private static final Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } }
如此看来,是不是饿汉式的单例写法非常的简单哦~
单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。
唯一的问题是该单例会在加载类时就对对象进行初始化,即未调用getInstance时已经初始化了,没有实现用时才初始化的懒加载模式。如果Singleton类不是很消耗资源,也无可厚非啦~
静态内部类
/** * 单例模式——静态内部类 * Created by jinzifu on 2017/3/26. */ public class Singleton { private static class SingletonChild { private static final Singleton instance = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return SingletonChild.instance; } }
此种单例模式写法,起到了懒加载的效果,并且在第一次调用getInstance时加载类Singleton到内存时就已初始化,确保了线程安全。
枚举
/** * 单例模式——枚举 * Created by jinzifu on 2017/3/26. */ public enum Singleton { INSTANCE; public void getName() { System.out.println("使用enum实现单例模式"); } }
注意:
1. 可以在枚举属性后面添加()来调用指定参数的构造方法,添加{}来实现其对应的匿名内部类。
2. 枚举式单例不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
这里任意写了一个方法getName(),外部可以如此调用:
Singleton singleton = Singleton.INSTANCE;
singleton.getName();
个人分述:单例模式的几种写法已经整理完毕,这里比较推荐静态内部类单例模式和枚举单例模式,饿汉式单例模式在目标类不是很消耗资源时也可以使用。这几种方式都是线程安全的。其他的方式不做推荐。
其他的设计模式我们接下来继续学习~
相关文章推荐
- 【Cocoa(mac) Application 开发系列之四】动作编辑器(Cocos2dx)制作流程详解及附上响应鼠标滚轴事件、反转坐标系、导入/创建资源目录等知识点代码!
- Java中关于包的知识点详解
- C++new/delete相关知识点详解
- java小知识点-为什么要配置环境变量,和怎么配置详解
- C语言数组基本知识点详解
- 多线程知识点详解
- C++new/delete相关知识点详解
- 知识点查缺补漏贴02:Linux环境fork()函数详解
- java单例模式详解
- SQL Server 数据库关键知识点详解(优秀经典)
- java单例模式详解
- java单例模式详解
- 动作编辑器(Cocos2dx)制作流程详解及附上响应鼠标滚轴事件、反转坐标系、导入/创建资源目录等知识点代码
- Java单例模式深入详解
- 【初学者必知必会】【电子技术:数电 模电 单片机】【基础概念和小知识点】详解
- 网络通信里面的套接字知识点 Send 和 Recv 详解
- 桌面倒计时2.0 涉及知识点详解及源码分享
- AndroidManifest.xml配置文件各个知识点详解
- JAVA面向对象知识点总结(3)—静态static、主函数详解、静态代码快、对象初始化过程
- MapReduce知识点详解