网上商城互联网网站开发,16岁0元开网店赚钱软件,潍坊网站设计好处,ps做的网站目录
#x1f6a9;单例模式
#x1f388;饿汉模式
#x1f388;懒汉模式
❗线程安全问题
#x1f4dd;加锁
#x1f4dd;执行效率提高
#x1f4dd;指令重排序
#x1f36d;总结 单例模式#xff0c;非常经典的设计模式#xff0c;也是一个重要的学科#x…目录
单例模式
饿汉模式
懒汉模式
❗线程安全问题
加锁
执行效率提高
指令重排序
总结 单例模式非常经典的设计模式也是一个重要的学科也是程序员必备的技能。
设计模式其实就是程序员的棋谱开发过程中会遇到”经典场景“针对这些经典场景
单例模式
单例实际上是单个实例(对象这种场景种希望有的类只能有一个对象不能有多个再这种场景下就可以使用单例模式了。
程序员不能手动自己设置一个单个对象确实可以但是编译器不相信你需要我们做监督确保这个对象不会出现多个出现多个的时候直接编译报错 比如我们前期学到的 final interfaceOverride,throw等等都是涉及到这里的思想方法。 饿汉模式
类加载的时候创建实例
在类的内部提供一个现成的实例。把构造方法设为private避免其他代码能够创建出实例。
通过上述方式就强制了其他程序员在使用这个类的时候就不会创建出多个对象了。
class SingTon{private static SingTon instancenew SingTon();//后续如果需要得到这个实例那么就可以直接调用getInstance()方法public static SingTon getInstance(){return instance;}//给构造方法设置成私有的此时类外面的其他代码就无法new其他实例了private SingTon(){};
} 得到实例的方法是被static修饰的所以只用依赖类来。 但是如果你创建对象的时候因为构造方法是私有的也是无法创建的。
所以这样就真正做到了饿汉模式“的单例模式. 懒汉模式
非必要不创建实例等需要了再创建
class SingLazy{private static SingLazy instancenull;public static SingLazy getInstance(){//首次调用getInstace()方法的时候才是创建if(instancenull){instancenew SingLazy();}return instance;}private SingLazy(){};
}
首先我们先不创建对象其指向空如果instace是null那么我们创建对象如果不是空那么就直接返回instace。 其实懒”也是意味着高效率省略了一些不必要的操作比如去上个厕所顺便去倒杯水喝。而不是想喝水立即去喝水。 就比如文本编译器(记事本比如需要打开一个非常大的文件10gb 1.先把所有的内容都加载到内存中然后再显示内容加载过程会很慢2.只加载一小部分数据到内存立即显示内容随着用户翻页再加载其他内容懒汉 介绍完懒汉模式和饿汉模式是如何实现单例模式的。
接下来我们来探究探究”懒汉模式“和”饿汉模式”俩种模式在线程安全中是否是安全的 ❗线程安全问题
加锁 这俩种写法是否有线程安全问题呢如果多个线程同时调用getInstance,是否会出问题呢 这俩种方式有一个是线程安全的一个是不安全的。 如果多个线程同时修改同一个变量此时就可能出现线程安全问题。如果多个线程同时读取同一个变量这个时候就没事~不会有线程安全问题。 我们之前学到了再多线程中对同一个变量进行修改的时候这时候会出现线程安全问题。 这个时候实例已经是多个了违背了单例的要求。
一旦这俩操作被穿插了就容易出现问题加锁的关键是要保证这俩操作是一个整体 那加锁的位置是在哪呢 一个加锁new是创建对象第二个加锁是将if和new的都加锁了。锁不是加了就线程安全加的对不对非常关键。 1锁的{}范围是合理的能够把需要作为整体的每个部分都囊括进去2锁的对象也得是能够起到合理的锁竞争的效果。 因为我们上述的线程中因为t1线程if成立了然后t2线程进行if和new操作此时new操作完了后t1线程剩下的部分继续进行我们只给new的部分加锁那么就依旧存在线程安全问题。我们需要将if 和new操作整体都加上锁才会避免穿插的情况。 但是一旦代码这样写后续每次调用getInstace就需要先加锁了但是实际上懒汉模式线程安全问题只是出现在最开始的时候对象没有new的情况一旦对象new出来了后续多线程调用getInstace就只有读操作就不会线程不安全了。其实加锁是一个开销很大的操作加锁就可能涉及到锁冲突的问题一冲突就会引起阻塞等待了某个代码涉及到加锁其实这个代码和高性能就冲突了。
如果多个线程情况下第一次对象是null此时创建好对象之后其他线程阻塞等待然后后面线程继续进行然后一直加锁if判断不成立就进行解锁然后其他线程又加锁这样如果有一百个线程进行那么就会有一百次加锁的情况那样性能方面是开销很大的。 执行效率提高
有没有什么办法既可以让代码线程安全又不会对执行效率产生太多的影响呢
在加锁语句的外层再引入一个if条件判定一下看看当前这里的锁是否要加上。
如果对象已经有了线程就安全了就不用加锁了。如果对象还没有存在线程不安全的风险就需要加锁。 if(instancenull){//首次调用getInstace()方法的时候才是创建synchronized (SingLazy.class) {if(instancenull){instancenew SingLazy();}}}
同样的条件连续写俩遍在别的地方没啥意义但是这个代码是非常有意义的也是非常重要的防止上述的执行效率很低。第一个if用来判定是否需要加锁第二个if用来判定是否需要new对象。
就是说第二个if确保只有一个线程去创建实例第一个if确保其他线程直接拿这个实例就行不用每次都在那一直傻傻等待。t1线程俩个if都判断成立了然后t2线程第一个if都进不去因为已经创建好对象了是否需要继续加锁。 指令重排序
指令重排序也可能会出现对上述的问题影响。编译器为了执行效率可能会调整原有代码的执行顺序调整的前提是要保持逻辑不变。 通常情况下指令重排序就能够保证逻辑不变的前提下把程序执行效率大幅度提高。单线程下好办多线程下可能会出现误判 new操作是可能会触发指令重排序的。 new操作可以拆分成三步 1.申请内存空间2.在内存空间上构造对象构造方法3.把内存的地址赋值给instance引用 可以按照123来执行也可以按照132来执行但是1肯定是执行的。 但是在多线程的情况下就可能有问题了。假设是按132执行的当t1执行完1和3时候此时Instance就已经非空了但是此时Instance指向的是一个还没初始化的非法对象。 此时此刻还没执行2呢t2就开始执行了t2判定instancenull条件不成立于是t2就直接return instance。进一步的t2线程的代码就可能会访问instance里面的属性和方法了。 但是instance是一个未初始化的非法对象如果t2线程访问的话就会出现bug。 这就相当于买房子的时候第一步是买房子第二步装修第三步是交钥匙最后是一个精装房但是如果我们按照这个顺序第一步是买房子第二步就交钥匙了打开之后只是一个毛胚房。 解决的方法就是我们之前学到的是volatile可以避免指令重排序问题。让volatile修饰Instance此时就可以保证Instance在修改过程中就不会出现执行重排序的现象了。
class SingLazy{private static volatile SingLazy instancenull;public static SingLazy getInstance(){if(instancenull){//首次调用getInstace()方法的时候才是创建synchronized (SingLazy.class) {if(instancenull){instancenew SingLazy();}}}return instance;}private SingLazy(){};
} 这样就解决了在创建对象的时候编译器优化的时候直接执行分配内存空间和把内存的地址赋值给insatance引用。但是中间的在内存空间中创建对象的一步直接被编译器优化了就不执行了。然后最后别的线程在调用的时候判断不成立 直接返回instance就会是一个没初始化的非法对象。如果用volatile修饰那么三步都操作没有编译器优化的现象了。 总结 在最开始的时候 一、多线程的情况下对同一个变量进行修改会出现线程安全的问题之后我们就需要加锁让其他线程阻塞等待 二、加锁的时候我们要注意到if和new俩个操作都得统一加锁在一起如果只给new加锁的话也依旧会出现问题。 三、加完锁之后我们发现线程t1判断之后instance不为空然后其他线程继续加锁不为空null然后解锁然后阻塞等待的线程继续加锁如果有一百个线程那么就有一百次加锁。这样会使执行效率降低所以我们就继续判断if这个if和内层的if判断的条件是一样的但是意义是不一样的第一个if是判断是否需要加锁,第二个if是判断是否创建这个对象。 四、我们还要考虑到指令重排序问题因为new操作会有三步分配内存空间让内存空间构造方法(创建对象内存的地址赋值给instance引用但是编译器会优化不进行内存空间构造方法直接分配完空间之后直接赋值给instance引用。这样就导致了t1线程拿到的instance是一个未初始化的非法对象但是非nullt2线程再继续进入俩层if不为空这样就返回了未初始化的非法对象这样就导致了bug就得需要用volatile修饰。 日子是自己的你开心它就会幸福。