海淀网站建设多少钱,企业注册类型,门户网站有哪些推广分类,茌平网站建设道气钻1.单例模式
什么是单例模式#xff1f;
所谓单例#xff0c;即单个实例。通过编码技巧约定某个类只能有唯一一个实例对象#xff0c;并且提前在类里面创建好一个实例对象#xff0c;把构造方法私有化#xff0c;再对外提供获取这个实例对象的方法#xff0c;#xff0…1.单例模式
什么是单例模式
所谓单例即单个实例。通过编码技巧约定某个类只能有唯一一个实例对象并且提前在类里面创建好一个实例对象把构造方法私有化再对外提供获取这个实例对象的方法方法名通常是用getInstance这个名称。 根据创建时机不同分为两种
1.类加载的时候创建也称为饿汉模式。
public class Singleton {//私有构造方法 禁止外界创建实例对象private Singleton() {};//唯一实例对象private static Singleton instance new Singleton();//为外界提供获取唯一实例的方法public static Singleton getInstance() {return instance;}}2.在第一次使用的时候创建也称为懒汉模式 但这种有线程安全问题。
public class SingletonLazy {//私有构造方法private SingletonLazy() {}//实例对象private static SingletonLazy instance null;//首次调用该方法时才真正创建出实例public static SingletonLazy getInstance() {if(instance null) {instance new SingletonLazy();}return instance;}}总结
高效性饿汉模式是在类加载的时候就会创建实例不管后面用不用得到都会创建出来。而懒汉模式是只有你真正用了才会创建出实例如果不用则不创建这样也就比较灵活也就省下了创建实例这一开销。
比如有个非常大的文档(10G)需要打开有两种方式打开
先把所有的内容都加载到内存中然后再显示内容。即饿汉。先只加载一部分数据到内存立即显示内容。随着用户翻页再动态加载其他内容。即懒汉。
为什么懒汉模式会有线程安全问题
先来说说线程安全问题产生的原因。
如果多个线程同时修改同一个变量就有可能出现线程安全问题。如果多个线程同时读取同一个变量是不会出现线程安全问题的。
饿汉模式中是直接创建实例并返回实例而懒汉模式是通过判断进行了修改既读又修改。这种判断再修改就可能会导致线程不安全问题因为可能会new多次创建多个实例的话就不是单例模式了。假设有两个线程 t1t2假如t1进行判断instance为null,准备new时这时候可能会出现t1还没new呢t2就开始判断instance是否为null。那此时instance肯定为null。这样的话实例就会被创建多次。显然这就违背了单例模式的要求单个实例。
如何使懒汉模式线程安全
进行加锁。
加锁也得注意咋加要看加的合不合适不是说加了就好了。
比如这种加锁
public class SingletonLazy {//私有构造方法private SingletonLazy() {}//实例对象private static SingletonLazy instance null;//首次调用该方法时才真正创建出实例public static SingletonLazy getInstance() {if(instance null) {synchronized (SingletonLazy.class) {instance new SingletonLazy();}}return instance;}}
缺点没有使 if判断 和 new操作 成为一个整体虽然在实例对象的时候加锁了但是线程在if判断的时候没有加锁还是会出现误判。假设有两个线程t1t2由于if判断并没有加锁两个线程是可以同时判断的如果t2线程刚好在t1线程判断instance为nullt1线程进入new之前或还没new完时t2进行if判断也是会创建多个实例对象的 这就导致虽然new的时候加了锁线程是顺序执行的但new外面的逻辑线程还是随机调度的。于是给整个if上锁。
public class SingletonLazy {//私有构造方法private SingletonLazy() {}//实例对象private static SingletonLazy instance null;//首次调用该方法时才真正创建出实例public static SingletonLazy getInstance() {synchronized (SingletonLazy.class) {//if和new成为一个整体if(instance null) {instance new SingletonLazy();}}return instance;}}
这样加锁弥补了上一个代码的缺点但是还有一个问题加锁这种操作就是把调度的随机性改为顺序执行了那效率性能必然会大打折扣况且我们把加锁放在最外面的话只要用到实例都要加锁而创建实例对象只有在首次时会发生线程不安全其实加锁一次就行用不着回回都进行加锁。这个代码线程虽然是安全了但是同时效率也降低了那么有没有一种既能使线程安全又能使效率比较快的代码逻辑呢当然有一种方法是在加锁外面再加一层if判断。即两层if。代码如下
public class SingletonLazy {//私有构造方法private SingletonLazy() {}//实例对象private static SingletonLazy instance null;//首次调用该方法时才真正创建出实例public static SingletonLazy getInstance() {if(instance null) {synchronized (SingletonLazy.class) {if(instance null) {instance new SingletonLazy();}}}return instance;}}
外面这层if就是用来判断对象是否创建好如果创建好了就不用进入外层if加锁直接执行return。代码效率一下提高。如果没创建好才会进入并加锁。而不是想上面代码频繁加锁。里面的if是判断是否需要new对象。
但是上面代码还是会有一个问题就是指令重排序问题。
指令重排序问题是什么
说到底和内存可见性一样都是编译器为了增加效率而对原有代码的执行顺序做出调整。调整的前提是保持逻辑不变。
举个例子假如我们去超市买东西需要买菜买衣服买首饰买玩具。此时若按照衣服玩具首饰菜这种顺序效率是最高的。 在单例模式中new操作时可能会触发指令重排序问题的new操作可分为三步
申请内存空间。在内空间上构造对象(构造方法)。给对象引用。
其中1的顺序不变2和3的顺序是可以换的。执行1-2-3顺序使我们希望的但若是执行1-3-2顺序执行到3时对象虽然不是null了但是此时的对象还没有初始化贸然使用是非法的。若有两个线程一个线程才执行到1-3另一个线程去使用还未new完的这个对象就会引发异常。
但虽然这样说我们不是加了锁吗那不应该是其他没加锁的线程阻塞等待加锁的这个线程执行完new的三步后释放锁后其他线程才能继续执行吗为什么我加锁的线程还没new完甚至是还没释放锁呢其他线程就已经去使用对象了原因是另一个线程压根就没进入外层if。一个线程加锁没拿到锁的等待不是锁一个线程拿到锁了不管其他线程在干嘛都得停下来等待而是执行到有synchronized语句时才等待。既然这个线程都没进入外层if肯定碰不到synchronized语句就会直接return实例就被拿去使用了。
上述问题的核心是解决指令重排序问题解决办法就是给实例对象加volatile修饰。
public class SingletonLazy {//私有构造方法private SingletonLazy() {}//实例对象private static volatile SingletonLazy instance null;//首次调用该方法时才真正创建出实例public static SingletonLazy getInstance() {if(instance null) {synchronized (SingletonLazy.class) {if(instance null) {instance new SingletonLazy();}}}return instance;}} 应用场景举例
外部资源每台计算机有若干个打印机但只能有一个PrinterSpooler以避免两个打印作业同时输出到打印机。内部资源大多数软件都有一个或多个属性文件存放系统配置这样的系统应该有一个对象管理这些属性文件 。 Windows的Task Manager任务管理器就是很典型的单例模式这个很熟悉吧想想看是不是呢你能打开两个windows task manager吗 不信你自己试试看哦~ windows的Recycle Bin回收站也是典型的单例应用。在整个系统运行过程中回收站一直维护着仅有的一个实例。 网站的计数器一般也是采用单例模式实现否则难以同步。 应用程序的日志应用一般都何用单例模式实现这一般是由于共享的日志文件一直处于打开状态因为只能有一个实例去操作否则内容不好追加。 Web应用的配置对象的读取一般也应用单例模式这个是由于配置文件是共享的资源。 数据库连接池的设计一般也是采用单例模式因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池主要是节省打开或者关闭数据库连接所引起的效率损耗这种效率上的损耗还是非常昂贵的因为何用单例模式来维护就可以大大降低这种损耗。 多线程的线程池的设计一般也是采用单例模式这是由于线程池要方便对池中的线程进行控制。 操作系统的文件系统也是大的单例模式实现的具体例子一个操作系统只能有一个文件系统。HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式所有的HttpModule都共享一个HttpApplication实例。