网站域名购买后能修改吗,深度苏州自媒体公司,电脑编程网站,资讯型电商网站优缺点目录 一、单例模式概述
二、懒汉模式和饿汉模式
1.饿汉模式
1.1代码实现
1.2实现细节
1.3模式优劣
2.懒汉模式
2.1代码实现
2.2实现细节
2.3模式优劣
三、多线程下的线程安全问题
1.懒汉和饿汉线程安全问题分析
1.1安全的饿汉模式
1.2不安全的懒汉模式
2.懒汉线程…目录 一、单例模式概述
二、懒汉模式和饿汉模式
1.饿汉模式
1.1代码实现
1.2实现细节
1.3模式优劣
2.懒汉模式
2.1代码实现
2.2实现细节
2.3模式优劣
三、多线程下的线程安全问题
1.懒汉和饿汉线程安全问题分析
1.1安全的饿汉模式
1.2不安全的懒汉模式
2.懒汉线程安全实现
2.1代码实现
2.2实现细节 一、单例模式概述
单例模式是一种创建型模式它的目的是确保一个类只有一个实例并提供一个全局访问点来获取这个实例。单例模式通常用于需要频繁创建和销毁同一对象的场景通过单例模式可以减少系统性能开销提高系统性能。
基础的单例模式分为两种懒汉模式和饿汉模式。
例如对于相当大的对象假设其管理10G的数据使用一次创建一次或是过多创建该对象都会造成较大的系统性能开销那我们能不能规定整个这个类只能创建出一个实例即每次创建实例返回的都是同一个对象这样不但避免了使用一次就创建一次的性能开销也能避免创建多个对象对空间资源的浪费。
可以结合下例进一步理解 对于一个“自助调料区”对象在火锅店中需要他的“客人”线程可以在任何时间任何地区同时同地的前来操作“自助调料区”对象获取需要的内容。
在这一场景下多个“自助调料区”对象无疑是没有必要的正是单例模式大显身手的地方采用了单例模式的火锅店就像是下达了“禁止多设调料区需要调料都到这一个调料区”的指令避免了资源的浪费。
说了这么多单例模式怎么使用又是怎么实现的呢
且看下文。
二、懒汉模式和饿汉模式
1.饿汉模式
饿汉模式是单例模式的一种简单实现‘式如其名’饿汉模式主打的就是一个饿死鬼投胎即类加载阶段就已经把实例创建出来了相当于程序已启动就有这个实例了总之非常迫切的感觉。
在饿汉模式中类加载的时候就已经实例化对象即“饿汉”在类加载时就完成了初始化因此可以保证只有一个实例存在。 1.1代码实现
虽然Java标准库没法直接规定类所能创建实例的数量但我们依然可以通过一些方法限制实例的创建间接达到只能创建一个实例的效果。
饿汉模式的代码具体实现如下
class Singleton {//实例为static修饰的类属性类加载阶段创建private static Singleton instance new Singleton();//通过getInstance方法获取唯一实例public static Singleton getInstance() {return instance;}//私有构造方法无法通过new创建该类实例private Singleton() {};
}
1.2实现细节
第二行代码“private static Singleton instance new Singleton();”
instance变量是Singleton类创建的唯一实例分别被‘private’关键字和‘static’关键字修饰。private保证了该变量为类所私有外界无法直接访问和修改只能通过下面的getInstance方法获取该实例。static表示类属性即instance作为Singleton类的属性在类加载阶段就被创建出来且具有唯一性。
第三~五行代码“getInstance()”
作为获取唯一实例的唯一方法存在需要由public和static修饰使外界可以通过类直接调用该方法。
第六行代码“private Singleton() {};”
private关键字使Singleton类的构造方法私有这样外界就没法new该类了。
1.3模式优劣
这种方式实现简单但会导致类加载时就创建对象如果不需要使用该对象则会造成资源浪费。同时由于实例化对象在类加载时完成因此无法在运行时改变实例状态。
2.懒汉模式
不同于饿汉模式的急不可耐懒汉模式采用的是摆烂策略就像博主暑假在家一样不喊我我就绝不出门妥妥的宅男。
在懒汉模式中类加载的时候不会实例化对象而是在第一次调用getInstance方法时才实例化对象。 2.1代码实现
class SingletonLazy {private static SingletonLazy instance null;//通过getInstance方法获取唯一实例public static SingletonLazy getInstance() {if(instance null){instance new SingletonLazy();}return instance;}//私有构造方法无法通过new创建该类实例private SingletonLazy() {};
}
2.2实现细节
上述实现和饿汉模式的实现差别不大只不过并没有在类加载阶段直接创建实例而是在第一次调用getIntance方法时才创建出实例即调用getIntance且instancenull未初始化时。
2.3模式优劣
只有在需要时才创建对象节省了系统资源只是实现上要比饿汉模式要更加复杂。由于在多线程环境下可能导致多个线程同时实例化对象因此需要加锁来保证线程安全。
三、多线程下的线程安全问题
上述懒汉模式和饿汉模式的实现针对的是单线程情况的代码多线程下代码实现是否会出现问题还需要具体分析。
1.懒汉和饿汉线程安全问题分析
1.1安全的饿汉模式
我们知道产生线程安全的原因可能是内存可见性、锁竞争、优化策略和线程调度策略及其它。具体产生问题的原因可能是多个线程对同一空间读写操作产生的。
再看饿汉模式的代码实现 很显然Singleton类中唯一的可调用方法getIntance只涉及到读操作并不会产生线程安全问题。而由于不涉及到锁更不会因为锁竞争陷入死锁。所以饿汉模式是线程安全的。
1.2不安全的懒汉模式
再看懒汉模式getIntance方法中不仅涉及了读操作同时也涉及了写操作这就为线程安全问题的产生埋下了隐患。 由于线程调度的随机性当两个线程在同一时间调用该方法时错落的执行顺序可能导致if语句出现不可避免的错判进而导致最终创建了两个SingletonLazy实例如下图 ①T1线程执行完if语句因为第一次调用getIntance方法intancenull所以T1线程接下来将要创建SingletonLazy实例并将其赋值给intance。
②轮到T2线程执行由于T1线程中尚未进行实例创建此时仍旧是instancenull所以if语句判断通过。接下来创建实例、赋值一气呵成最后还将创建的Singleton对象返回。
③再次轮到T1线程继续执行创建了一个和T2线程不同的Singleton实例引用赋给instance。最后这个引用又被返回。
综上所述在多线程情况下竟然出现了两个懒汉实例这不符合单例模式下一个类只能创建一个实例的原则很可能产生无法预估的错误妥妥的bug代码。所以单线程下实现的懒汉模式不是线程安全的。
2.懒汉线程安全实现
上文分析懒汉模式代码线程不安全的原因是进程的随即调度问题这一点我们可以通过引入锁来保证代码的原子性一个整体。同时还要注意其他线程安全问题。
2.1代码实现
class SingletonLazy {//锁对象private static Object lock new Object();//唯一实例新增的volatile关键字是为了禁止指令重排序导致bugprivate static volatile SingletonLazy instance null;public static SingletonLazy getInstance() {//当instance不为空时不进行加锁提高代码效率if(instance null) {//通过锁保证创建实例代码的原子性不会因为线程的随即调度而产生多个实例synchronized(lock) {//多线程情况下可能由于锁竞争陷入阻塞所以其他线程可能创建过实例了if(instance null){//虽然new SingletonLazy可以分解为三个指令// 但instance受volatile保护不会指令重排序instance new SingletonLazy();}}}return instance;}private SingletonLazy() {};
}
2.2实现细节
通过锁保证代码块的原子性进而克服系统随机调度的问题 ①T1线程执行。实例未创建if判断通过。
②轮到T2线程。实例未创建if判断通过。T2线程首先获取到锁资源synchronized代码块执行完毕后才释放锁。if判断通过实例创建成功后锁释放。
③锁释放T1线程解除阻塞获取到锁资源。由于T2线程已经创建实例成功if判断不通过不创建新的实例解锁返回instance。
④轮到T2线程返回instance的值。
上述过程返回的是同一个实例成功克服系统随机调度的问题。
通过额外的if嵌套提高代码效率
对于单线程代码来说两个完全一样的if判断就是在脱裤子放屁多此一举。但对于多线程代码来说由于线程的随机调度线程阻塞等问题紧邻的两行代码执行时间相隔的可能是海枯石烂。
就我们的代码来说外层if的作用是在实例已经创建的情况下如果再调用本方法只需经过该if语句就可以直接返回值结束方法。像较于加锁解锁if作为跳转语句效率相对非常之高可以提高代码的运行效率。
而内层if则是判断线程阻塞时其他线程没有创建实例确保只创建出一个实例。
通过volatile关键字保障创建操作不会因为代码优化指令重排序产生问题 指令重排序是因为编译器会在保持“代码逻辑不发生变化”这一前提下对我们的代码进行优化举个形象的例子 1.去楼下超市买菜 2.回家 3.下楼倒垃圾 假如我们的代码执行逻辑为1-2-3代码优化过后可能执行逻辑就变为1-3-2两种执行逻辑效果相同但效率却大大提高了。 而在多线程代码中代码优化却可能会导致bug的出现。例如当线程频繁对同一个变量进行读值在代码优化过后可能就不会再从主内存中读值而是直接从线程的寄存器中读值这时如果修改主内存的值线程是感知不到的从而导致线程安全问题的出现。 new Singleton()可以分解为以下三个指令
1.申请一段内存空间
2.在这个内存上调用构造方法创造出这个实例
3.把这个内存地址赋值给instance引用变量
指令重排序会在保持“代码逻辑不发生变化”这一前提下对我们的代码进行优化。对于逻辑而言上述三个指令的顺序123和132都是没有区别的因此执行顺序可能被优化成132。
程序执行是一条指令一条指令执行的因此三条指令执行过程中线程可能就会被调度走了如下 ①执行完毕后instance不为空但引用指向的空间还未初始化因为指令2还未执行。
②因为instance不为空外层if判断未通过返回未初始化空间的引用instance方法结束。因为实列未初始化而初始化的时间又无法确定随机调度T1要和其他线程竞争这时候使用这个实例就可能产生问题。
③执行时间不确定可能产生问题。
想要避免上述情况的出现就必须保证指令的执行顺序保持不变为123想达到这一效果可以使用volatile关键字修饰instance利用其禁止指令重排序的特性。
博主是Java新人每位同志的支持都会给博主莫大的动力如果有任何疑问或者发现了任何错误都欢迎大家在评论区交流“ψ(∇´)ψ