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,主要看评论
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341

推荐阅读更多精彩内容

  • 动机 有些情况下,一个类只能有一个实例是很重要的。比如说,在操作系统中只能有一个窗口管理器的(文件系统或打印机程序...
    holysu阅读 1,254评论 0 0
  • 1 场景问题# 1.1 读取配置文件的内容## 考虑这样一个应用,读取配置文件的内容。 很多应用项目,都有与应用相...
    七寸知架构阅读 6,618评论 12 68
  • #文鹏天天读书# 生活中就有这样的事:你接连数月每天都碰到一个人,于是你同他的关系便十分亲密起来,你当时甚至会想没...
    文鹏天天读书阅读 159评论 0 0
  • 今天突然感到很迷茫困惑,于是我就好好的整理下我的婚姻爱情观。 我现在31单身,没有过真正意义上的女朋友,现在婚姻已...
    温暖你的胸口阅读 221评论 0 0
  • 足球和乒乓 文:recycler 在微信群里说起了巴西,我说了句,巴西乒乓打不过中国,中国足球不及巴西。同学回了句...
    Recycler阅读 258评论 0 0