1 定义:单例模式是指一个程序的运行中只有一个实例,并且提供一个全局访问点
2 单例模式:
-
饿汉模式:程序启动后,立即创建对象,不存在线程安全问题,但可能会造成资源浪费
当程序启动后一定会用到此类,我们选择饿汉模式
-
懒汉方式:当有程序调用单例对象的时候才初始化
当我们使用一些工具类,优先考虑使用懒汉模式,可避免资源被提前加载到内存中
3 单例模式的实现:
- 创建一个私有的构造函数(防止其他类直接new此对象
- 创建一个私有的属性对象
- 创建一个公共的对外暴露得单例对象
4 饿汉模式:
public class DataSourceSingleton {
// 1.提供私有构造方法
private DataSourceSingleton() {
}
// 2.创建一个私有属性对象(随着jvm的启动而启动)
private static DataSourceSingleton dataSource=new DataSourceSingleton();
// 3.提供公共对外的单例对象
public static DataSourceSingleton getInstance(){
return dataSource;
}
}
5 懒汉模式
5.1 懒汉模式版本1:
public class DataSourceSingleton2 {
// 1.提供私有构造方法
private DataSourceSingleton2() {
}
// 2.创建一个私有属性
private static DataSourceSingleton2 dataSource;
// 3.提供公共对外的单例对象
public static DataSourceSingleton2 getInstance(){
if(dataSource==null){//多个线程触发 线程不安全
dataSource=new DataSourceSingleton2();
}
return dataSource;
}
public static void main(String[] args) {
Thread t1=new Thread(()->{
System.out.println(DataSourceSingleton2.getInstance());
});
Thread t2=new Thread(()->{
System.out.println(DataSourceSingleton2.getInstance());
});
t1.start();
t2.start();
}
}
/**
DataSourceSingleton2@2314afc1
DataSourceSingleton2@2d2d936c
**/
-
在公共获取实例的方法内判断是否为第一次访问,这样当程序启动后不会立马实例化,而是等调用时才会初始化,防止了资源浪费
-
这样解决了资源浪费问题,但是带来了线程不安全的问题
线程 t1 t2 第一次访问 if(dataSource==null) 结果为true 第二次访问t2获得时间片 还未实例化对象,t2进入if判断也为true
5.2 懒汉模式版本2
给getInstance() 方法加锁:
public class DataSourceSingleton2 {
// 1.提供私有构造方法
private DataSourceSingleton2() {
}
// 2.创建一个私有属性
private static DataSourceSingleton2 dataSource;
// 3.提供公共对外的单例对象
public static synchronized DataSourceSingleton2 getInstance(){
if(dataSource==null){
dataSource=new DataSourceSingleton2();
}
return dataSource;
}
}
- 直接给方法加锁,导致无论是什么时候访问都要排队,大大降低了执行效率
5.3 懒汉模式版本最终版3
使用双重校验锁
public class DataSourceSingleton3 {
// 1.提供私有构造方法
private DataSourceSingleton3() {
}
// 2.创建一个私有属性
private static DataSourceSingleton3 dataSource = null;
// 3.提供公共对外的单例对象
public static DataSourceSingleton3 getInstance() throws InterruptedException {
if (dataSource == null) { //双重校验锁
synchronized (DataSourceSingleton3.class) {
if(dataSource==null){
dataSource = new DataSourceSingleton3();
}
}
}
return dataSource;
}
}
-
在第一次判断处加锁,然后再进行一次判断,这样只有在第一次访问时才会排队执行,提高执行效率
-
由于创建一个对象的操作过程是非原子性的
1 在内存中开辟空间
2 初始化对象:实例变量初始化、实例代码块初始化、以及构造函数初始化
3 设置对象到相应的内存地址
所以如果发生指令重排序,线程1的执行顺序可能为 1、3、2,获得时间片的线程2会将线程1未初始化(2)但已分配内存地址(3)的对象返回(dataSource!=null -> return dataSource)