目录
1.设计模式是什么?
2.单例模式
1.概念:
2.如何设计一个单例
1.口头约定(不靠谱)
2.使用编程语言的特性来处理
3.使用"饿汉模式"设计单例
1.详细步骤
2.完整代码
4.使用"饿汉模式"设计单例
1.详细步骤
2.完整代码
4. "饿汉模式"和"懒汉模式"的区别
1.设计模式是什么?
设计模式就是厉害的程序员根据以往的设计经验,总结出来的一套方法,类似于棋谱
2.单例模式
1.概念:
单例模式就是一种设计模式
单例在全局范围内只有一个实例化对象
例如在java通过JDBC连接数据库是使用的DateSource类,在这个类中定义了数据库的用户名,密码,连接串,定义好了以上的属性就可以通过DateSource的实例化对象获取数据路的链接
2.如何设计一个单例
1.口头约定(不靠谱)
对外提供一个方法,要求大家使用的时候,通过这个方法来获取
不能保证每个人都遵守规定,所以不采用
2.使用编程语言的特性来处理
首先我们思考一下,在Java中,哪些对象是唯一的?
1.class对象类对象,比如String.class
2.用static修饰的变量是类的成员变量
所有的实例对象中,访问的都是同一个成员变量
所以,通过类对象与static配合就可以实现单例的目的
3.使用"饿汉模式"设计单例
类似于这种随着类的加载一起完成初始化的方式,称之为"饿汉模式"
"饿汉模式"书写简单,不容易出错
1.详细步骤
1.我们让其在类加载的时候完成初始化,那么所有对象之间共享这个实例
2.既然是单例,全局唯一的对象,那么就不能通过new去获取新对象
将构造方法私有化,就可以避免外部new这个类的新对象
3.我们对外部提供一个获取对象的方法,每次调用返回的都是同一个实例对象
将方法用ststic修饰,使其可以通过类名直接调用
2.完整代码
public class Singleton {
public static Singleton instance = new Singleton();
private Singleton(){};
public static Singleton getinstance() {
return instance;
}
}
4.使用"懒汉模式"设计单例
为了避免程序启动的时候浪费过多的系统资源,我们可以暂时先不初始化这个实例对象,等到程序使用的时候在对他进行初始化
1.详细步骤
最初我们并不对其进行初始化,而是在使用的时候进行初始化
但此时出现了一个问题,在多线程下去获取单例对象,出现了两个以上的实例化对象,这并不符合单例模式,说明我们的代码造成了线程不安全的现象
分析造成线程不安全的原因
没有保证初始化实例对象操作的原子性
为了解决这个问题,我们给初始化的代码上锁
目前看来似乎解决了问题
但是出现了一个更严重的问题,初始化实例对象这部分代码块,在整个程序的调用过程中,只需要执行一次就足以,但是按照我们现在的写法,只要外部调用了getinstance()方法,那么所有的线程都需要参与锁竞争,都必须停下来一个个地执行,然而锁竞争是非常耗费资源的,这势必会造成大量的资源浪费,这与我们的预期也是不符的
这里我们需要知道一个知识
用户态与内核态
为了避免过度消耗系统资源,我们可以在加一层判断
双重检查锁,这两层if语句判断的目的是不一样的
第一个if语句用来判断实例对象是否是还未进行初始化,若还未初始化,因为只能初始化一次,所以加锁,只允许一个线程去执行初始化操作,其余线程等待,
第二个if语句用来判断被第一条if语句遗漏的线程,此时这个线程已经获取到instance为空,但却并未争抢到执行机会,使得其他线程先一步初始化成功对象实例,此时当这个线程终于争抢到CPU执行机会时,我们需要对它重新判断,因为此时实例对象已初始化完成,不需要再次进行初始化
到现在我们通过synchronized关键字解决了原子性,内存可见性,那么有序性我们又该如何保证呢?
那么就需要给共享变量加volatile关键字
有人不理解,上面测试明明已经是同一个实例化对象了,为什么我们还要多此一举呢?
让我们用时间线来展开说明
2.完整代码
public class Singleton01 {
private static Singleton01 instanse = null;
private Singleton01(){};
public static Singleton01 getinstance() {
if (instanse == null) {
synchronized (Singleton01.class) {
if(instanse == null) {
instanse = new Singleton01();
}
}
}
return instanse;
}
}
4. "饿汉模式"和"懒汉模式"的区别
1.工作中可以使用 "饿汉模式",因为书写简单,不容易出错
2."饿汉模式"在程序加载的时候一起完成初始化,但由于计算机资源有限,为了节约资源,可以使用"懒汉模式"加载
3.懒汉模式在多线程情况下可能会出现不安全的问题
4.我们可以使用synchronized包裹初始化的代码块
5.但初始化实例对象的代码块只执行一次,后续线程在调用getinstance()方法时,依然会产生锁竞争,频繁的进行用户态与内核态之间的切换,非常的耗费计算机资源
6.使用双重检查锁,在最外层加一个非空校验,避免无用的锁竞争
7.此时还存有有序性的隐患,若计算机对指令重排序,可能会带来不可预计的错误
8.synchronized解决了原子性,内存可见性,但是解决不了有序性,所以给共享变量加一个volatile修饰,禁止计算机对指令的重排序