Singleton 单例模式

单例概念
  • 单例模式属于创建者模式,该模式提供了一种最佳的创建对象方式,为何最佳??
  • 单例是指对某个类而言,该类负责自己创建自己,同时确保只有唯一的对象被创建
  • 同时该类对外提供访问该唯一实例的方式外界不能重复创建,取用即可
实际意义
  • 全局只需要该类的唯一对象即可,节省系统资源内存开销
  • 案例:一个公司只需一个老板;创建的一个对象需要消耗太多资源,如与数据库连接
  • 在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡驱动的对象通常设计成单例,这些应用都或多或少具有资源管理器的功能,即统一供各使用方使用,所有操作都在此处,避免不一致的状态,避免政出多头
  • 单例对象通常作为程序中存放配置信息的载体,因为它能保证其他对象读到一致的信息
  • 如服务器程序中,该服务器的配置信息可能存放在数据库或文件中,这些配置数据由某个单例对象统一读取,服务器中其他对象若要获取这些配置信息,只需访问该单例对象即可,但对该单例的访问可能涉及到同步问题
具体实现
  • 饿汉式
      1. 线程安全
      1. 类加载即初始化,基于classloader 机制避免了多线程同步问题
      1. static getInstance接口是触发类加载的一种方式,此刻是实例的lazy loading,其他触发类加载则**达不到lazy loading 的效果
      1. 容易产生垃圾对象
/**
 * 饿汉单例模式
 */
public class MyHungerSingleton {

    //static:类加载时期即初始化 singleton,全局唯一性
    private static MyHungerSingleton singleton = new MyHungerSingleton();

    //私有构造函数,避免外界new 实例
    private MyHungerSingleton() {

    }

    //对外提供的获取实例接口,该方法第一次调用则会导致类加载,从而初始化 static singleton 变量,
    //后面调用时,singleton 变量已初始化成功,直接使用即可,所以无需判断
    public static MyHungerSingleton getInstance() {
        return singleton;
    }

}
  • 懒汉式
    • 延迟加载,只在getInstance 接口调用时才会初始化唯一实例
    • 非线程安全
    • if判断与其后的实例赋值语句在CPU层面会有时间间隔,这段时间差是导致多线程问题的根本原因???
/**
 * 懒汉单例模式
 */
public class MyLazySingleton {

    //static 全局变量,但类加载时不初始化
    private static MyLazySingleton myLazySingleton = null;

    //私有构造函数
    private MyLazySingleton() {

    }

    //方法1:线程不安全
    public static MyLazySingleton getInstance() {
        //判断全局实例是否存在,若不判断,则会新产生一个实例,之前的实例对象仍存在,只是无指针指向
        //不判断则会重复创建,浪费资源
        //if判断与其后的实例赋值语句在CPU层面会有时间间隔,这段时间差是导致多线程问题的根本原因
        if (myLazySingleton == null) {
            myLazySingleton = new MyLazySingleton();
        }
        return myLazySingleton;
    }

}
  • 解决懒汉的多线程问题
    • 延迟加载,第一次调用初始化,避免内存浪费
    • 多线程安全
    • 对方法加锁效率低???
    • getInstance()性能 对应用程序不是很关键???
public class MyLazySingleton {

    //static 全局变量,但类加载时不初始化
    private static MyLazySingleton myLazySingleton = null;

    //私有构造函数
    private MyLazySingleton() {

    }

    ......

    //方法2:线程安全,效率低
    //添加Synchronized 关键字,只允许同一段时间内只有一个线程进入该方法
    //拒绝出现多线程同时if 判断和赋值的操作
    //针对的是整个方法
    public static synchronized MyLazySingleton getInstance() {
        //if判断仍需要断定全局是否已经存在singleton 变量
        if (myLazySingleton == null) {
            myLazySingleton = new MyLazySingleton();
        }
        return myLazySingleton;
    }
}
  • 双检锁/双重校验锁
    • lazy 初始化
    • 多线程安全
    • 高性能???
    • getInstance() 的性能对应用程序很关键???

锁1

public class MyLazySingleton {

    //static 全局变量,但类加载时不初始化
    private static MyLazySingleton myLazySingleton = null;

    //任意锁对象
    private static Object synLock = new Object();
    
    //私有构造函数
    private MyLazySingleton() {

    }
    
    ......
    
    //方法3:线程安全,效率高
    public static MyLazySingleton getInstance3() {

        if (myLazySingleton == null) {
            //静态方法内不能锁 this 对象,故单独使用了一个static 对象
            synchronized (synLock) {
                if (myLazySingleton == null) {
                    myLazySingleton = new MyLazySingleton();
                }
            }
        }
        return myLazySingleton;
    }

}

锁2

    //方法3:线程安全,效率高
    public static MyLazySingleton getInstance3() {

        if (myLazySingleton == null) {
            //synchronized (synLock) {
            //直接锁class 对象
            synchronized (MyLazySingleton.class) {
                if (myLazySingleton == null) {
                    myLazySingleton = new MyLazySingleton();
                }
            }
        }
        return myLazySingleton;
    }
附:懒汉无加锁环境到枷锁环境整个过程的问题展现
public class MyLazySingleton {

    private static MyLazySingleton myLazySingleton = null;

    private MyLazySingleton() {

    }

    ......

    //多个线程执行该代码
    public static MyLazySingleton getInstance1() throws InterruptedException {

        if (myLazySingleton == null) {
            //制造创建单例前的准备工作,放大判断语句和赋值语句时间间隔
            Thread.sleep(2000);
            myLazySingleton = new MyLazySingleton();
        }
        return myLazySingleton;
    }
}
public class MySingletonDemo extends Thread{

    public void run() {
        //MyHungerSingleton singleton = MyHungerSingleton.getInstance();
        MyLazySingleton singleton = null;
        try {
            singleton = MyLazySingleton.getInstance1();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //打印出每个线程得到的 单例对象 的 hashcode
        System.out.println("ThreadName: " + Thread.currentThread().getName() + ", hashcode: " + singleton.hashCode());
    }

    public static void main(String[] args) {

        Thread[] testThreads = new Thread[10];

        for(int i = 0; i< 10; i++) {
            testThreads[i] = new MySingletonDemo();
        }

        for (Thread item : testThreads) {
            //启动每个线程
            item.start();
        }

    }
}

1) 输出

ThreadName: Thread-9, hashcode: 915343504
ThreadName: Thread-8, hashcode: 1516995504
ThreadName: Thread-7, hashcode: 1127673581
ThreadName: Thread-5, hashcode: 1905381423
ThreadName: Thread-6, hashcode: 1211216510
ThreadName: Thread-4, hashcode: 982120049
ThreadName: Thread-3, hashcode: 1222692380
ThreadName: Thread-1, hashcode: 936272565
ThreadName: Thread-2, hashcode: 1098437177
ThreadName: Thread-0, hashcode: 726367991

2) 注释掉ThreadSleep代码后结果,此刻由于判断与赋值间隔很短,出现了一致的效果

ThreadName: Thread-0, hashcode: 726367991
ThreadName: Thread-1, hashcode: 726367991
ThreadName: Thread-2, hashcode: 726367991
ThreadName: Thread-3, hashcode: 726367991
ThreadName: Thread-4, hashcode: 726367991
ThreadName: Thread-5, hashcode: 726367991
ThreadName: Thread-6, hashcode: 726367991
ThreadName: Thread-7, hashcode: 726367991
ThreadName: Thread-8, hashcode: 726367991
ThreadName: Thread-9, hashcode: 726367991

3) 为方法加上 synchronized 关键字

ThreadName: Thread-4, hashcode: 915343504
ThreadName: Thread-1, hashcode: 915343504
ThreadName: Thread-7, hashcode: 915343504
ThreadName: Thread-2, hashcode: 915343504
ThreadName: Thread-8, hashcode: 915343504
ThreadName: Thread-5, hashcode: 915343504
ThreadName: Thread-3, hashcode: 915343504
ThreadName: Thread-9, hashcode: 915343504
ThreadName: Thread-6, hashcode: 915343504
ThreadName: Thread-0, hashcode: 915343504

4) 使用 synchronized 代码块

-----------------------------------------------------------------------------
synchronized (MyLazySingleton.class) { //synchronized位置 1,达到同步效果
    if (myLazySingleton == null) {
    
        //创建单例前的准备工作
        Thread.sleep(3000);
        myLazySingleton = new MyLazySingleton();
        
    }
}

-----------------------------------------------------------------------------

if (myLazySingleton == null) {
    //synchronized位置 2,达不到同步效果
    //因为多个线程都已经执行完判断,都判断到实例为空,都排队等着new 一个呢
    //所以最后生成的实例都不同
    synchronized (MyLazySingleton.class) { 
    
        //创建单例前的准备工作
        Thread.sleep(3000);
        myLazySingleton = new MyLazySingleton();
    
    }
}

-----------------------------------------------------------------------------

if (myLazySingleton == null) { //判断 1
    //synchronized位置 3
    synchronized (MyLazySingleton.class) { 
        //判断 2,再次进行判断,因为之前最开始的判断可能失效
        //因为其他线程已经释放锁且 new 值了此刻已经不为 null 
        //避免二次赋值
        if (myLazySingleton == null) { 
            //创建单例前的准备工作
            Thread.sleep(3000);
            myLazySingleton = new MyLazySingleton();
        }
    }
}

-----------------------------------------------------------------------------
volatile 变量作用
  • 线程可见性,A线程对线程共享变量的修改能立即被B线程感知,即A修改后,B能立刻读取最新修改值
    • 读:A线程读取volatile 修饰的变量时,会从主线程重新拉取,保证子线程与主线程一致
    • 写:A线程修改volatile 变量时,会在修改结束后,刷新到主线程中,使主线程为最新值
  • 禁止指令重排序
    • A a = new A() 触发类加载
      • 执行顺序非原子性
          1. 为A 对象分配内存空间
          1. 对象的初始化
          1. a 引用指向 内存空间
      • 理想顺序1->2->3
      • 实际可能为 1->3->2,此刻对象还没有完全初始化,但a 引用已经!=null,有了内存地址,导致对a 的操作是针对不完整对象的
双检锁情况下 volatile 变量的使用
  • 双检锁不一定完全正确
  • 原因在于两次判断时,所判断变量皆是线程局部变量,而非主线程变量
  • 或者说,其他线程对 static 实例的修改new 操作未必立即刷新到 主线程中
  • 从而导致A线程已经new,但未刷新到主线程, 但B线程第二次判断是并不知晓
private static volatile MyHungerSingleton singleton;
参考:http://blog.csdn.net/xuewater/article/details/42266385
参考:http://blog.csdn.net/cselmu9/article/details/51366946
参考:https://www.cnblogs.com/damonhuang/p/5431866.html,主要看评论

你可能感兴趣的:(Singleton 单例模式)