网站网页制作机构,手机怎么分享wordpress,桂林生活网官网首页,装修公司合作平台的网站单例模式
什么是设计模式呢#xff1f; 设计模式就好比棋手中的棋谱。在日常开发中#xff0c;会会遇到很多常见的“问题场景”#xff0c;针对这些问题场景#xff0c;大佬们就设计了一些固定套路#xff0c;按照这些固定套路来实现代码或应对这些问题场景#xff0c;也…单例模式
什么是设计模式呢 设计模式就好比棋手中的棋谱。在日常开发中会会遇到很多常见的“问题场景”针对这些问题场景大佬们就设计了一些固定套路按照这些固定套路来实现代码或应对这些问题场景也不会吃亏。这些固定的套路就是设计模式。 单例模式就是设计模式中的一个非常经典的一个设计模式也是校招中最容易考到的设计模式。
单例模式可以保证某个类在程序中只有唯一 一个实例化对象不会存在多个实例化对象。
单例模式的实现方式有很多种最常见的有“饿汉模式”和“懒汉模式”这两种。 饿汉模式
饿汉模式就是讲究一个迫切在饿汉模式中要求在加载类的同时创建实例。
实现方式在该类中使用static修饰类成员对象并将该类的构造方法用private修饰使其私有化并提供一个方法来获得对象。
实现代码
class Singleton{private static Singleton instancenew Singleton();public Singleton getInstance(){return instance;}private Singleton(){}
} 懒汉模式 懒就是尽量晚的创建实例甚至不创建实例也就是延迟创建实例这样就方便我们根据实际需求来创建合适的实例。
实现方式在该类中先让static修饰的类成员置为null然后通过方法来创建类的实例和获取实例。
代码实现
class SingletonLazy{private static SingletonLazy instancenull;public SingletonLazy getInstance(){if(instancenull){instancenew SingletonLazy();}return instance;}private SingletonLazy(){}
}线程安全分析 在多线程的情况下饿汉模式和懒汉模式是否存在线程安全问题
判断饿汉模式和懒汉模式是否存在线程安全问题主要是分析这两种模式的getInstance()方法是否存在线程安全问题。
饿汉模式
由于懒汉模式中的getInstance()方法中只有一个return语句这是一个读操作所以不会涉及线程安全问题。 懒汉模式
由于懒汉模式中的getInstance()方法中涉及到修改操作在多线程程序中有可能产生线程安全问题。
1.原子性分析
如下图 如上图在多线程情况下就会出现上图这种情况 这样就会导致第一次new的对象被第二次new的对象给覆盖掉了第一个线程new出来的对象就会被GC释放掉了。
这里假设我们我们new的过程中要将一个很大内存的数据加载到内存中本来加载一份这样的数据就要花费很多时间但由于上述问题的存在可能就要加载两份的数据结果第一份数据还被释放掉了这样反而降低了程序的运行效率。
这里产生线程安全的原因是条件判断和修改操作不是原子的这时我们就可以通过加锁将判断操作和修改操作打包成原子的。
如下图 引入加锁后后执行的线程执行到加锁的位置就会阻塞等到前一个线程执行完毕释放锁时此时instance就不为null了所以第二个线程就不会执行new操作了这样就避免出现加载两份new出相同对象的情况了提高了程序的效率。
2.锁效率分析 当我们加锁之后就会引入一个新的问题。
当我们的instance已经实例化好之后多个线程在继序执行代码的时候为了判断instance是否已经实例化就会多次的加锁去执行所里面的判断操作多个线程持续的加锁和解锁就会出现阻塞一旦阻塞对于计算机来说阻塞的时间间隔就是沧海桑田这样影响程序的效率。
解决方法
我们可以按需加锁真正涉及到线程安全问题的时候我们在加锁不涉及到线程安全问题的时候我们就不加锁。
以上面的的的代码为例
我们真正涉及到线程安全问题的时候是第一次实例化instance的时候当我们第一次实例化成功后后面执行的线程就不必再去加锁进去所里面执行实例化的操作了也就是说第一次instance成功实例化之后后面线程涉及的操作就不涉及线程安全问题了所以我们就可以让后面执行的线程跳过加锁的操作。
class SingletonLazy{private static SingletonLazy instancenull;public SingletonLazy getInstance(){if(instancenull){synchronized (this){if(instancenull){instancenew SingletonLazy();}}}return instance;}private SingletonLazy(){}
} 注意 这里出现了两次if(instancenull) syncronized里面的if(instancenull)是为了判断是否需要实例化对象最外面的if(instancenull)是为了在多线程程序中instance已经实例化好的情况下其他线程继续执行该代码的时候不需要再继续实例化让其跳过加锁和解锁的操作直接执行return语句使程序不会阻塞提高程序的运行效率。 3.内存可见性分析
上面代码在多线程情况下会不会出现内存可见性的问题呢
如下图 由于编译器优化是一个非常复杂的过程我们无法确定是否出现内存可见性问题但是为了杜绝内存可见性的问题我们还是要用volatile关键字来修饰类成员 。 4.指令重排序分析
这里更关键的问题是指令重排序问题。
指令重排序也是编译器优化的一种体现编译器优化能保证在代码逻辑不变的情况下会改变代码指令执行的先后顺序来提高代码的运行效率。
如上面代码中的instancenew SingletonLazy();这个语句就有可能触发指令重排序的问题。 这条语句执行的指令主要有3条 1. 申请内存空间 2. 在空间中构造化对象也就是初始化对象 3.将内存空间的“首地址”赋值给引用变量 正常的执行顺序是1-2-3但是由于指令重排序的问题会出现1-3-2这样的执行顺序也就是instance不为null了但是还没初始化里面的内容此时就会导致其他线程拿着一个未初始化的对象进行其他操作这样就会导致线程安全问题。
针对指令重排序的问题我们也是通过volatile来修饰类成员来解决。 volatile关键字的作用 1.确保程序成内存中读取数据避免内存可见性问题 2.确保程序对变量的读取和修改不会出现指令重排序的问题。 懒汉模式的完整版代码
class SingletonLazy{private static volatile SingletonLazy instancenull;public SingletonLazy getInstance(){if(instancenull){synchronized (this){if(instancenull){instancenew SingletonLazy();}}}return instance;}private SingletonLazy(){}
}