【Java多线程】总结(四)单例模式之 懒汉模式 饿汉模式 线程安全问题

1 定义:单例模式是指一个程序的运行中只有一个实例,并且提供一个全局访问点

2 单例模式:

  1. 饿汉模式:程序启动后,立即创建对象,不存在线程安全问题,但可能会造成资源浪费

    当程序启动后一定会用到此类,我们选择饿汉模式

  2. 懒汉方式:当有程序调用单例对象的时候才初始化

    当我们使用一些工具类,优先考虑使用懒汉模式,可避免资源被提前加载到内存中

3 单例模式的实现:

  1. 创建一个私有的构造函数(防止其他类直接new此对象
  2. 创建一个私有的属性对象
  3. 创建一个公共的对外暴露得单例对象

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
**/
  1. 在公共获取实例的方法内判断是否为第一次访问,这样当程序启动后不会立马实例化,而是等调用时才会初始化,防止了资源浪费

  2. 这样解决了资源浪费问题,但是带来了线程不安全的问题

    线程 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;
    }
}
  1. 直接给方法加锁,导致无论是什么时候访问都要排队,大大降低了执行效率

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. 由于创建一个对象的操作过程是非原子性的

    1 在内存中开辟空间

    2 初始化对象:实例变量初始化、实例代码块初始化、以及构造函数初始化

    3 设置对象到相应的内存地址

    所以如果发生指令重排序,线程1的执行顺序可能为 1、3、2,获得时间片的线程2会将线程1未初始化(2)但已分配内存地址(3)的对象返回(dataSource!=null -> return dataSource)

你可能感兴趣的:(笔记,多线程,java,后端)