湘潭网站建设 搜索磐石网络,上海做网站品牌公司,网站功能优化,有哪些网站是可以接单做任务的目录
一. 设计模式
二. 单例模式
2.1 饿汉模式
2.2 懒汉模式
a. 加锁synchronized
b. 双重if判定
c. volatile关键字#xff08;双重检查锁定#xff09; 一. 设计模式 设计模式是在软件工程中解决常见问题的经典解决方案。针对一些特定场景给出的一些比较好的解决…目录
一. 设计模式
二. 单例模式
2.1 饿汉模式
2.2 懒汉模式
a. 加锁synchronized
b. 双重if判定
c. volatile关键字双重检查锁定 一. 设计模式 设计模式是在软件工程中解决常见问题的经典解决方案。针对一些特定场景给出的一些比较好的解决方案只要按照设计模式来写代码就可以使代码不会太差保证了代码的下限。 设计模式比较适用于CJavaC#但是对于 Python 或 Erlang 这些语言这里的很多设计模式都是不适用的。设计模式适合具有一定的编程经验之后再去学习如果缺少编程经验会比较难以理解。 二. 单例模式 单例模式 是Java中最简单的设计模式之一它确保一个类只有一个实例并提供一个全局访问点。一个Java程序中某个类要求只有唯一一个实例适合使用单例模式单例模式前提是“一个进程中”如果有多个Java进程自然每个进程中都可以有一个实例了
在Java中实现单例模式主要有两种方式“饿汉模式”和“懒汉模式” 单例模式三部曲 static 修饰 instance 成员变量类变量构造方法私有静态全局访问点 2.1 饿汉模式 在饿汉模式中单例对象在类加载时就被立即初始化。这意味着类加载完成后单例对象就已经创建好了不管你是否需要它。
public class SingletonEager {//静态实例变量private static SingletonEager instance new SingletonEager();//构造方法私有private SingletonEager(){}//全局访问点每次需要通过getInstance来获取实例的public static SingletonEager getInstance(){return instance;}
}
饿汉模式中的 “饿” 的意思是 “迫切”eager即在类被加载的时候就会创建出这个单例的实例。
优点
简单易实现类加载时就完成了实例化避免了线程安全问题
缺点
如果自始至终未使用过这个实例则会造成内存浪费
2.2 懒汉模式 在懒汉模式中单例对象在第一次使用时才进行初始化。这种方式可以延迟对象的创建只有在实际需要时才会创建对象。
public class SingletonLazy {//静态变量声明时不初始化private static SingletonLazy instance;//构造方法私有private SingletonLazy(){}//初次使用会进行初始化public static SingletonLazy getInstance(){if(instance null){instance new SingletonLazy();}return instance;}
}懒汉模式中的 “懒” 的意思是推迟了创建实例的时机首次调用getInstance才会创建实例 计算机中谈到的懒是褒义词意思是效率会更高能不搞就不搞很多时候就可以把这部分开销就省下了
相比饿汉模式懒汉模式的效率会更高一些。 比如有一个编辑器打开一个非常大1G的文本文档 一启动就把所有的文本内容都读取到内存中然后再显示到界面上 [饿汉]启动之后只加载一小部分数据一个屏幕能显示的最大数据随着用户进行翻页操作再按需加载剩下的内容 [懒汉] 很明显懒汉模式会造成巨大的开销而懒汉模式开销更小~~ * 但是如果在多线程下调用getInstance 是否会有线程安全问题呢—— 多个线程针对一个变量进行修改如果只是读取则没有问题先判定再修改的这种代码模式属于典型的线程不安全代码判定和修改之间可能涉及到线程的切换。 当线程A初次调用 getInstance判断 instance 为null会执行创建实例但还未执行就被调度走了的情况下此时调度线程B线程B刚好执行 getInstance此时判断 instance 仍然为 null又会执行一次创建实例。 这样同一个进程中就会执行两次创建实例的操作。虽然第二次创建覆盖了 instance 的值使得第一次创建的实例没有引用指向很快就会被垃圾回收机制给消除掉instance 的引用仍是唯一的但这样多次new操作已经造成了线程安全问题~~ 因此需要对判断和初始化这两个操作打包成原子的..... a. 加锁synchronized
public class SingletonLazy {private static SingletonLazy instance;private SingletonLazy(){}public static SingletonLazy getInstance(){//将判断和初始化操作打包成原子操作加锁synchronized (SingletonLazy.class){if(instance null){instance new SingletonLazy();}}return instance;}
} 注意此处的 return 操作不用加到同步代码块中。因为无论 return 操作是在本线程还是其他线程return的值都是Instance内存中最新的值 加锁之后确实解决了线程安全问题但是加锁同样也可能带来阻塞..... 如果上述代码已经 new 完对象了if 的判断分支再也进不去了后续的代码 都是单纯的 读操作已经没有线程安全问题了。但是以后只要调用 getInstance 都会触发加锁操作但在第一次初始化后其实已经没有必要加锁了。加锁以后还会产生阻塞影响到性能。 因此针对这个问题还要进一步改进——通过条件判断在应该加锁的时候才加锁不需要加锁的时候直接跳过加锁 b. 双重if判定
public class SingletonLazy {private static SingletonLazy instance;private SingletonLazy(){}public static SingletonLazy getInstance(){//再加一个if判断instance不为null直接返回值即可不用加锁阻塞if(instance null){synchronized (SingletonLazy.class){if(instance null){instance new SingletonLazy();}}}return instance;}
}
以往写的代码中从未遇到过同样的if 连着写
曾经的单线程代码中这样的写法是毫无意义的两个条件值一定相同。 但是在多线程代码中任意两个代码之间都可能穿插其他线程的逻辑。synchronized会使代码出现阻塞一旦阻塞之后啥时候恢复执行是无法预知的。在这个过程中很可能其他线程就把这个值给修改了~~ 这俩条件只是恰好写法一样实际上作用是完全不同的~~ 外面的 if 判定 是否要加锁里面的 if 是判定 是否要创建对象 只不过巧了在这个代码中通过同样的方式完成上述两种判定的~~ 但是这个代码可能还会因为指令重排序编译器优化引起线程安全问题如果是单线程代码编译器都能准确的进行判断如果是多线程代码编译器也是可能出现误判 在创建实例 instance new SingletonLazy () 时这行代码实际上可以分解为以下三个操作 1. 分配内存空间 2. 初始化对象 3. 将对象引用指向分配的内存空间执行后instance值不再为null 在没有volatile关键字的情况下编译器和处理器可能会对上述操作进行重排~~ 例如 a. 分配内存空间 b. 将对象引用指向分配的内存空间执行后instance值不再为null c. 初始化对象 如果发生了这种重排假设 线程A 正在执行 instance new SingletonLazy ()而 线程B 几乎同时调用 getInstance 方法可能会发生以下情况 线程A 执行了 a 和 b此时 instance 已经不为 null但是对象还没有被初始化线程B 进入 getInstance 方法执行第1次检查发现 instance 不为 null因此直接返回 instance线程B 在使用未完全初始化的 instance 时可能会遇到空指针异常或其他错误。 在Java中所有的对象引用默认值都是null如果声明了一个对象引用但没有进行初始化那么试图访问这个引用的任何方法或属性都会抛出空指针异常。 此处相当于引用已经不为null了但是没有为 instance 中的属性和方法进行初始化因此使用 instance 的时候会抛出空指针异常。 因此为了解决这个问题要对 instance 变量用 volatile关键字 修饰 * “指令重排序”的详细讲解在 JavaEE初阶6 * volatile 关键字的特性保证“可见性”与“有序性”不保证“原子性”详细讲解在JavaEE初阶8 c. volatile关键字双重检查锁定
public class SingletonLazy {//volatile关键字修饰instanceprivate static volatile SingletonLazy instance;private SingletonLazy(){}public static SingletonLazy getInstance(){if(instance null){synchronized (SingletonLazy.class){if(instance null){instance new SingletonLazy();}}}return instance;}
} volatile关键字 的作用之一就是禁止对 volatile变量 前后的操作进行指令重排。这样当线程A创建 SingletonLazy 实例时即使发生了指令重排其他线程在读取 instance 变量时也能保证看到的是完全初始化后的对象。volatile 不仅保证了变量的可见性还防止了指令重排可能带来的问题。 换个角度看加上volatile之后也能禁止对 instance 赋值操作 插入到其他操作之间。 总结 设计模式软件开发中针对一些特定套路给出的特定解决方案 单例模式 饿汉模式程序启动类加载的时候就会创建实例——不涉及线程安全问题懒汉模式在程序第一次使用这个实例的时候才会创建实例——涉及到线程安全问题 a. 加锁把if判定和new赋值操作打包成原子操作 b. 双重 if 判定解决加锁的性能问题 c. volatile关键字解决指令重排问题