举报企业网站用个人信息备案,品牌网站建设只詢大蝌蚪,呼伦贝尔市建设局网站,如何做微信小程序开发前言 什么是单例模式#xff1f; 其实用通俗的话就是程序猿约定俗成的一些东西#xff0c;就比如如果你继承了一个抽象类#xff0c;你就要重写里面的抽象方法#xff0c;如果你实现了一个接口#xff0c;你就要重写里面的方法。如果不进行重写#xff0c;那么编译器就会…前言 什么是单例模式 其实用通俗的话就是程序猿约定俗成的一些东西就比如如果你继承了一个抽象类你就要重写里面的抽象方法如果你实现了一个接口你就要重写里面的方法。如果不进行重写那么编译器就会报错。这其实就是一个规范。 而单例模式能保证某个类在程序中只存在唯一的一个实例而不会创建出多个实例 那么单例模式又分成“饿汉”和“懒汉”两种、
一.饿汉模式
顾名思义饿汉模式就是在类加载的时候创建实例。 package thread;
//期待这个类能有唯一实例
public class hungryDemo {private static hungryDemo instance new hungryDemo();public static hungryDemo getInstance() {return instance;}//把构造方法设置为私有,这样在类外就无法 new 出这个对象的实例了private hungryDemo() {}
} 代码解读 1. 首先创建了一个 hungryDemo 类里面有一个类方法和一个类变量 2. 我们将构造方法设置为了private那么在类外就无法再针对 hungryDemo 再实例化类了 我们现在在类外通过 hungryDemo提供的 public static hungryDemo getInstance 方法来进行调用可以发现如下结果 class Demo1 {public static void main(String[] args) {hungryDemo h1 hungryDemo.getInstance();hungryDemo h2 hungryDemo.getInstance();System.out.println(h1 h2);}
} 运行结果 可以发现两者获取到的类对象引用是一致的那么单例模式的饿汉版本就创建好了。 二.懒汉模式
单线程版本
我们的预期结果是不变的那就是要实现单例模式也就是这个类 只能被实例化一次
那么懒汉模式顾名思义也就是类加载的时候不创建实例第一次使用的时候才创建实例。
那么我们可以写出以下代码 package thread;public class lazyDemo {private static lazyDemo instance null; public static lazyDemo getInstance() {/*** 只有调用该方法的时候才创建对象*/if (instance null) {instance new lazyDemo();} return instance;}private lazyDemo() {}
}代码解读 首先设置类成员变量 instance 为 null当第一次使用getInstance()的时候才进行创建对 象。 其次跟饿汉模式一样将类的构造方法设置为 private 类外无法再次创建对象。 最后在getInstance方法中判断 instance 是否为空为空那就创建对象。为空说明已经 被调用一次了那么就直接返回 instance 引用。 多线程版本 1
在以上的单线程版本中我们不难发现以下问题
假设现在有两个线程他们是按照如下的顺序来执行的 那么此时的代码就会出现问题 t1 线程首先判断了 instance 是否为空此时 t2 线程来运行了也判断 instance 是否为空紧接着 instance不为空然后就创建了对象 然后再回到 t1 线程中又要进行创建对象。 此时问题已经很明显了那就是 由于if代码块在多线程中的执行顺序问题导致的
更精简一下
就是 instance new lazyDemo() 是写操作 instance null 是读操作在多线程中如果一段代码即涉及读操作又设计写操作那么就很容易出现问题 解决办法 当一段代码是因为读写操作出BUG我们首先想到的就是加锁。也就是在我写的时候你不要 读。我读的时候你不要写。
synchronized 是一种内置的 Java 关键字它用于实现线程的同步。当一个线程进入synchronized块或方法时它获得了锁这会阻止其他线程同时进入相同的synchronized块或方法从而确保了共享资源的互斥访问。
修改代码如下 package thread;public class lazyDemo {private static lazyDemo instance null; public static lazyDemo getInstance() {/*** 只有调用该方法的时候才创建对象*/synchronized (lazyDemo.class) { //1. 加锁解决的是线程安全问题(确保是单例模式,只new一次)if (instance null) {instance new lazyDemo();}}return instance;}private lazyDemo() {}
} 对于对象lazyDemo.class实际上就是lazyDemo这个类也就是对类进行加锁。 此时加锁之后当t1线程进行读写操作的时候t2线程再次进行访问就只能进行阻塞。 此时t1就可以放心创建出一个对象出来此时t2再进行调用方法的时候instance 不为空就直接返回 t1 创建好的对象引用。 这时候就确保了只创建出一个实例。 多线程版本2
其实多线程版本1 还是有问题的我们发现如果t1 线程加锁后创建好了对象其他线程t2t3t4.........在进行访问的时候首先就要进行加锁操作。 也就是每次访问都要进行加锁这是一个资源开销非常大的操作。
深入探究一下我们发现其他线程t2t3t4.........在进行访问的时候只需要判断当前的对象是否被创建好了即可。如果被创建好了那么就直接返回对象引用。如果没有被创建好再进行加锁创建对象。
修改代码如下 public class lazyDemo {private static lazyDemo instance null; public static lazyDemo getInstance() {/*** 只有调用该方法的时候才创建对象*/if(instance null) { //2. if判断解决的是多次加锁,加锁频率太高的问题synchronized (lazyDemo.class) { //1. 加锁解决的是线程安全问题(确保是单例模式,只new一次)if (instance null) {instance new lazyDemo();}}}return instance;}private lazyDemo() {}
} 在多线程中这两个 if 的作用大不相同 修改后我们发现如果 t1 线程创建好了对象 此时其他线程t2t3t4.........在进行调用的时候首先判断了instance 是否为空不为空就说明已经创建好了对象~ 多线程版本3
其实到现在这个懒汉模式的单例代码还是有问题 在多线程下要考虑到编译器的优化问题当编译器没有按照我们的逻辑进行操作的时候那么就会出现问题。
在此代码中new 操作可以分为以下三步
1.申请内存空间一定先执行获取到内存地址
2.在内存空间上构造对象构造方法
3.把内存的地址赋值给 instance 引用
在单线程环境下执行那种顺序都无所谓但是如果在多线程环境下就可能出现问题
假设是按照 1 3 2 的顺序来执行当 1 和 3 操作执行完的时候instance 已经非空了只是内存空间上还没有构造对象 / 方法此时instance 指向的是一个还没初始化的非法对象。 此时此刻 t2 进行访问判断 instanc 是不为空的然后就返回了一个还没初始化的非法对象进一步 t2 线程就有可能访问 instance 里面的属性和方法。此时就出现了问题了。
这个问题就是指令重排序问题解决办法就是让 instance 加入上volatile 关键字此时就避免了指令重排序问题。 //3.加 volatile是为了解决new 操作的指令重排序问题
private volatile static lazyDemo instance null; 此时的代码就会严格按照 1 2 3 的顺序执行。 总结单例模式是一个约定俗成的规范保证一个类只能实例化一个对象。饿汉模式在多线程和单线程都没有问题因为一开始它就创建好了对象。 而懒汉模式的多线程版本会出现以下三个问题1. 线程安全问题 确保只new 一次2. 多次重复加锁的问题 3. 指令重排序问题。
希望以上的解决办法对你有所帮助