Java设计模式——死磕单例模式
来源:互联网 发布:matlab求逆矩阵 编辑:程序博客网 时间:2024/05/17 08:24
单例模式
单例模式,顾名思义,在程序运行过程中,只允许某个对象存在一个实例,使用单例模式,你可以
- 确保一个类只有一个实例
- 提供了一个对象的全局访问指针
- 在不影响单例类的客户端的情况下允许将来有多个实例
传统的单例模式有三种,懒汉式、饿汉式和 登记式。
懒汉式
懒汉式的特点是延迟加载,在需要用到实例的时候去加载。我们书写一个最简单的懒汉式展开谈论,代码如下:
`public class Tom { private static Tom tom=null; private Tom(){//私有化构造,保证类外不可以创建实例 } public static Tom getInstance(){//返回实例 if(tom==null){ tom = new Tom(); } return tom; }}`
这段代码理解虽然简单,但有一处要提一下这种懒汉式其实是不安全的,当多线程并发的调用getInstance()还是有可能会创建两个实例,我们尝试给方法加上锁,修改如下:
`public class Tom { private static Tom tom=null; private Tom(){//私有化构造,保证类外不可以创建实例 } //添加锁,保证线程同步 public static synchronized Tom getInstance(){ if(tom==null){ tom = new Tom(); } return tom; }}`
以上修改虽然保证真正只有一个实例,但是我们实现的方法是加锁,势必会影响到其他线程访问的效率,聪明的人们就又想到了双重判断,所以下面的代码也就是最常看到的单例模式
`
public class Tom {
private static Tom tom=null;
private Tom(){//私有化构造,保证类外不可以创建实例
}
public static synchronized Tom getInstance(){ if(tom == null){ synchronized (Tom.class){ if(tom == null){ tom = new Tom(); } } } return tom; }}`
这段代码看起来很完美,理由如下:
- 如果检查第一个tom不为null,则不需要执行下面的加锁动作,提高了程序的性能;
- 如果第一个tom为null,即使有多个线程同一时间判断,但是由于synchronized的存在,只会有一个线程能够创建对象;
- 当第一个获取锁的线程创建完成后tom对象后,其他的在第二次判断tom一定不会为null,则直接返回已经创建好的tom对象;
但是以上依然存在问题,让我回顾一下创建对象过程
- 创建变量
- 开辟内存地址
- 将内存地址赋值给变量
但是因为重排序的问题,可能会出现
- 创建变量
- 将内存地址赋值给变量
- 开辟内存地址
当第一个线程经过两次判断开始创建对象时,假如此时发生重排序,创建一个变量,将地址赋值给这个变量,这时如果有一个线程执行getInstance,在第一次判断时,tom不为null,所以直接return tom,但是此时的tom,仅仅只是一个地址,那么在使用时,调用这个对象的某个方法,却发现这个变量指向的内存,什么都没有!这时会抛什么异常呢!
问题如上,既然知道了问题源头,那么想办法解决就好了,解决办法其实很简单,只需要加上volatile修饰就行了
`public class Tom { //通过volatile修饰 private volatile static Tom tom=null; private Tom(){//私有化构造,保证类外不可以创建实例 } public static synchronized Tom getInstance(){//返回实例 if(tom == null){ synchronized (Tom.class){ if(tom == null){ tom = new Tom(); } } } return tom; }}`
当tom声明为volatile后,步骤2、步骤3就不会被重排序了,也就可以解决上面那问题了。
这种解决方案的实质是:允许步骤2和步骤3重排序,但是不允许其他线程看见。
饿汉式
饿汉式的特点是一开始就加载了。变量
tom
是使用static修饰的,只会在程序启动的时候new出一个实例,以后每次getInstance()都返回同一个tom
`public class Tom { //设立静态变量,直接创建实例 private static Tom tom = new Tom(); private Tom(){ //私有化构造,保证类外不可以创建实例 } public static Tom getInstance(){ return tom; } }`
登记式
1.新建父类,定义map集合,用来存子类的实例
`public class Person { // 设立静态变量,直接创建实例 private static Map<String, Person> map = new HashMap<String, Person>(); // -----受保护的-----构造函数,不能是私有的,但是这样子类可以直接访问构造方法了 protected Person() { } public static Person getInstance(String name) { if (name == null) { name = Person.class.getName(); } if (map.get(name) == null) { try { map.put(name, (Person)Class.forName(name).newInstance()); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } return map.get(name); } }`
2.新建子类,子类继承父类,用包名做key将实例保存在父类的map中
` public class Boy extends Person{ public static Boy getInstance() { return (Boy)Boy.getInstance(Boy.class.getCanonicalName()); } } public class Girl extends Person{ public static Girl getInstance() { return (Girl)Girl.getInstance(Girl.class.getCanonicalName()); } }`
3.测试代码
` //测试代码 public static void main(String[] args) { Boy boy1 = Boy.getInstance(); Boy boy2 = Boy.getInstance(); System.out.println(boy1==boy2); Girl girl1 = Girl.getInstance(); Girl girl2 = Girl.getInstance(); System.out.println(girl1==girl2); }`
总结
所以有人总结,如果说懒汉式是“时间换空间”,那么饿汉式就是“空间换时间”,因为一开始就创建了实例,所以每次用到的之后直接返回就好了。
- Java设计模式——死磕单例模式
- Java设计模式—工厂设计模式
- java设计模式——代理模式
- JAVA设计模式——工厂模式
- Java设计模式——观察者模式
- Java设计模式——组合模式
- JAVA设计模式——迭代器模式
- Java设计模式——命令模式
- JAVA设计模式——命令模式
- JAVA设计模式——适配器模式
- JAVA设计模式——外观模式
- Java设计模式——策略模式
- JAVA设计模式——策略模式
- java设计模式——策略模式
- java设计模式——装饰模式
- java设计模式——代理模式
- java设计模式——原型模式
- java设计模式——外观模式
- 使用Kotlin实现百思不得姐弹出菜单
- CentOS 安装 Redis 和 php redis.so 扩展笔记
- C语言知识简介
- linux上安装redis服务
- 1143 字母转换
- Java设计模式——死磕单例模式
- UINavigationBar添加渐变的背景颜色
- 精美UI静态界面欣赏
- C语言和C++结构体的定义
- Maven学习 (二) Eclipse 上安装 Maven3插件
- FME2015破解+MyFME+汉化
- 初入C语言——关于C语言的简单认知
- Linux多线程编程讲解之系列十
- spring security的原理及教程