单例模式是啥?
单例模式就是保证某个类在程序中只存在唯一一份实例对象。
单例模式的实现方式有哪些?
饿汉模式:正如字面,表示很饿,要赶紧吃饭;所以它在类加载的同时,就会创建实例对象。
懒汉模式:表示很懒,能不干就不干;所以它在类加载的同时不会创建实例对象;直到第一次去使用的时候,才会创建实例对象。
饿汉模式的实现
- 优点:不存在线程安全问题
- 缺点:资源浪费(当不使用实例时,但他又创建了)
/**
* DataSource 单列模式
* 饿汉模式:类加载的同时,创建实例
*/
public class DataSourceSingleton {
//1.创建一个私有的实例对象
private static DataSourceSingleton dataSourceSingleton=new DataSourceSingleton();
//2.私有的构造函数(防止外部new实例对象);否则就成多例了
private DataSourceSingleton(){
}
//3.对外提供这个实例对象
public static DataSourceSingleton getInstance(){
return dataSourceSingleton;
}
}
懒汉模式的实现
- 优点:不浪费资源
- 缺点:存在安全问题:当有两个线程同时访问getInstance()方法,都执行到了if(dataSourceSingleton1==null),此时,他们都会去new一个对象,则就不是单列模式了
/**
* DataSource 单列模式
* 懒汉模式:第一次使用的时候才去创建实例对象
*/
public class DataSourceSingleton1 {
//1.创建私有的属性
private static DataSourceSingleton1 dataSourceSingleton1;
//2.2.私有的构造函数(防止外部类new实例对象);否则就成多例了
private DataSourceSingleton1(){
}
//3.创建对外提供实例对象的方法
public static DataSourceSingleton1 getInstance(){
if(dataSourceSingleton1==null){
dataSourceSingleton1=new DataSourceSingleton1();
}
return dataSourceSingleton1;
}
}
为了保证它是单例模式,可以给其上锁synchronized
public synchronized static DataSourceSingleton1 getInstance(){
if(dataSourceSingleton1==null){
dataSourceSingleton1=new DataSourceSingleton1();
}
return dataSourceSingleton1;
}
可但这样的话,线程就得一个一个去访问getInstance()方法,效率低
懒汉模式的改进
public class DataSourceSingleton2 {
//1.用volatile 修饰 dataSourceSingleton2
private static volatile DataSourceSingleton2 dataSourceSingleton2;
private DataSourceSingleton2(){
}
//2.使用双重if,降低锁的竞争频率
public static DataSourceSingleton2 getInstance(){
if(dataSourceSingleton2==null){
synchronized (dataSourceSingleton2){
if(dataSourceSingleton2==null){
dataSourceSingleton2=new DataSourceSingleton2();
}
}
}
return dataSourceSingleton2;
}
}
为啥要使用双重if?
- 当有多个线程同时调用getInstance()方法时;假设线程1和线程2一起调用getInstance()方法;首先他们都判断到dataSourceSingleton2==null(第1次的if),
- 然后他们都去竞争锁synchronized (dataSourceSingleton2),假设线程1竞争到了锁,线程1进行(第2次的if)就去创建了实例对象 ,然后释放了锁;
- 这之后,线程2得到了锁,进行(第2次if)发现实例对象已经被创建过了,就不会再去new对象了,这样就保证了程序的单例模式。
为啥要用用volatile 修饰 dataSourceSingleton2?
volatile除了保证内存可见性,还可以防止指令重排序
首先了解一下dataSourceSingleton2=new DataSourceSingleton2()
,它由3部分组成:
- 给对象分配内存
- 初始化对象
- 设置对象到相应的内存地址上
- 当程序先添加了线程1,让线程1执行,当线程1判断,上锁,再判断,然后执行到dataSourceSingleton2=new DataSourceSingleton2()时;
- 但发生了指令重排序:变成 1、3、2;还没有执行2时,程序又添加了线程2,让线程2执行
- 线程2首次判断dataSourceSingleton2==null时,发现对象已经被创建了,则会返回dataSourceSingleton2对象
- 但此时虽然线程1创建了这个对象,但并没有初始化这个对象
- 所以线程2得到的对象并不是完整的