单例模式
来源:互联网 发布:qt tcp编程 编辑:程序博客网 时间:2024/05/18 02:06
单例模式:一个类只能创建一个对象的设计模式
单例模式代码实现
懒汉模式
/* * 要想让一个类只能构建一个对象,则不能让他随便进行new,因此构造方法是私有的 * 懒汉模式(如果单例初始值为null,则构建单例对象) */public class Singleton { //私有构造函数 private Singleton() { } //单例对象 private static Singleton instance = null; //静态工厂方法 public static Singleton getInstance() { if(instance == null) { instance = new Singleton(); } return instance; }}
以上创建单例模式的方法是非线程安全的
假设Singleton类刚刚被初始化,instance对象还是空,这时候有两个线程A和B同时访问getInstance()方法。因为instance为空,所以两个线程都通过了条件判断,开始执行 new new Singleton()操作,致使instance被创建了两次。
饿汉模式
/* * 饿汉模式:单例对象一开始就被new Singleton1()主动创建,不再进行判空操作 */public class Singleton1 { //私有构造函数 private Singleton1() { } //单例对象 private static Singleton1 instance = new Singleton1(); //静态工厂方法 public static Singleton1 getInstance() { return instance; }}
线程安全的单例模式
为了实现线程安全,在懒汉模式上我们进行改进,即防止new Singleton()被执行多次,这里可以使用Synchronized来进行同步。
public class Singleton2 { //私有构造函数 private Singleton2() { } //单例对象 private static Singleton2 instance = null; //静态工厂方法 public static Singleton2 getInstance() { //双重检测 if(instance == null) { //使用同步锁,锁住整个类 synchronized (Singleton2.class) { if(instance==null) { instance = new Singleton2(); } } } return instance; }}
上面这种写法事实还是存在一定问题的
JVM编译器会对指令进行重排
instance=new Singleton()
其实在执行时分三步:
1、首先分配对象的内存空间
2、初始化对象
3、设置instance指向刚分配的内存地址
但是并不是一定按照1,2,3的顺序进行执行,CPU和JVM可能会对指令进行重排。
假设按照1,3,2的顺序进行执行。则线程A执行完1,3后,instance对象并未进行初始化,但是它已经不再指向null,此时如果线程B执行if判断,则结果是false,直接return instance,这里反悔了一个没有初始化完成的instance对象。
为了避免这种情况的发送,即避免指令重排,我们可以在instance对象前加上修饰符volatile
volatile阻止了变量访问前后的指令重拍,保证了指令执行顺序。
volatile在多处理器开发中保证了共享变量的可见性。
由于现在的操作系统,都有缓存机制。当一个线程执行时,它会维护一个私有的堆栈,与公共堆栈中的数据并不是时刻保持一致的。正是由于缓冲区和内存区数据的不一致性,导致程序执行结果出现差别。volatile通过内存屏障,强制从公共堆栈中取得变量的值。
内存屏障指令在多核处理器下会引发两件事:
将当前处理器缓存行的数据写会到系统内存
这个写会内存的操作会使其他CPU里缓存了该内存地址的数据无效。
public class Singleton3 { //私有构造函数 private Singleton2() { } //单例对象 private volatile static Singleton2 instance = null; //静态工厂方法 public static Singleton2 getInstance() { //双重检测 if(instance == null) { //使用同步锁,锁住整个类 synchronized (Singleton2.class) { if(instance==null) { instance = new Singleton2(); } } } return instance; }}
静态内部类实现
public class Singleton3 { private Singleton3(){} private static class LazyHolder{ private static final Singleton3 INSTANCE = new Singleton3(); } public static Singleton3 getInstance() { return LazyHolder.INSTANCE; }}
从外部是无法访问静态内部类LazyHolder的 ,只有调用Singleton.getInstance()方法,才能得到单例对象INSTANCE。这是种懒加载的方式,只有执行Singleton.getInstance(),INSTANCE对象才被初始化。
以上的方式都有一个问题,就是无法防止利用反射来重新构建对象
先来看一下如何利用反射打破单例的约束
public class BreakSingletonByReflect { public static void main(String[] args) throws Exception{ //先获取单例类的构造器 Constructor<Singleton3> con = Singleton3.class.getDeclaredConstructor(); //设置改构造器为可访问 con.setAccessible(true); //构造两个不同的对象 Singleton3 singleton1 = con.newInstance(); Singleton3 singleton2 = con.newInstance(); System.out.println("两个对象是否相等"+singleton1.equals(singleton2)); }}
输出结果是:两个对象是否相等false
如何防止反射来重建单例对象?使用枚举的方式
用枚举实现
public enum SingletonEnum{ INSTANCE; private SingletonEnum() { .... .... .... }}
参考http://mp.weixin.qq.com/s/2UYXNzgTCEZdEfuGIbcczA
- 单例、单例模式
- 单例模式-多线程单例模式
- 单件模式(单例模式)
- 设计模式------单例模式
- 设计模式------单例模式
- 设计模式-单例模式
- 设计模式 - 单例模式
- 设计模式---单例模式
- 设计模式---单例模式
- PHP模式-单例模式
- 【设计模式】单例模式
- 设计模式-单例模式
- 设计模式----单例模式
- 设计模式--单例模式
- 设计模式-单例模式
- 单例模式(单子模式)
- 设计模式-单例模式
- [设计模式] 单例模式
- Windows平台下,编译Curl+OpenSSL
- 初识java这个小姑娘(三)
- 一个新的起点~规划
- js无缝滚动
- 前端UI框架小汇总
- 单例模式
- 斐波那契数列的三种算法以及复杂度
- 购物车
- 深度学习之常见tricks
- REST解释
- 几个字符串函数的实现
- Bezier基函数与幂函数之间的关系
- .NET Framework 自动内存管理机制深入剖析 (C#分析篇)
- Anaconda3 5.0.1 无法启动spyder, jupyter notebook