营销型网站建设供货商,西地那非片能延时多久有副作用吗,缓存图片 wordpress,贵阳市做网站公司文章目录 #x1f4a1;volatile保证内存可见性#x1f4a1;单例模式#x1f4a1;饿汉模式#x1f4a1;懒汉模式#x1f4a1;懒汉模式多线程版#x1f4a1;volatile防止指令重排序 #x1f4a1;volatile保证内存可见性
Volatile 修饰的变量能够保证“内存可见性”以及防… 文章目录 volatile保证内存可见性单例模式饿汉模式懒汉模式懒汉模式多线程版volatile防止指令重排序 volatile保证内存可见性
Volatile 修饰的变量能够保证“内存可见性”以及防止”指令重排序“
什么是可见性当某个线程修改了某个共享变量其他的线程是否可以看见修改后的内容 因为访问一个变量时CPU就会先把变量从内存中读出来然后放到CPU寄存器中进行运算运算完之后再将新的数据在内存中进行刷新 对于操作系统来讲读内存的速度是比较慢的,(注意这里的慢 是 相对于寄存器而言的就像读内存要比读硬盘快上千倍或上万倍读寄存器比读内存快上千倍上万倍) 这时候就会影响执行的效率。为了提高效率编译器就会对代码进行一个优化把读内存的操作优化成读寄存器从而减少对内存的读取提高整个效率
举个例子
代码目的创建两个线程通过线程2修改线程1的循环判断条件来终止线程1的循环执行
public class Demo1 {private static int flag 0;public static void main(String[] args) {Thread thread1 new Thread(() - {while(flag 0) {//当循环不等于0时一直循环直到flag被改变}System.out.println(thread1 执行结束);});Thread thread2 new Thread(() - {Scanner in new Scanner(System.in);System.out.println(更改flag);//通过更改flag终止线程1的执行flag in.nextInt();System.out.println(输入成功);});thread1.start();thread2.start();}
}根据结果可以看到线程1并没有终止循环这就是“内存可见性”所导致的线程不安全 在多线程的环境下(在单线程环境下没问题如果编译器作出优化可能就会导致bug虽然提高了效率但是最后结果却是错误的
此时就需要程序员使用Volatile关键字告诉编译器不需要进行代码优化
直接给flag加上Volatile即可 注意 volatile只能够保证内存可见性问题不会保证代码的原子性但是Synchronized既可以保证内存可见性也能保证原子性
以上就是volatile能够保证内存可见性的讲解
单例模式
单例模式是一种经典的设计模式了它的作用就是保证在有些场景下需要一个类只能有一个对象而不能有多个对象比如像你以后娶媳妇你娶媳妇肯定是只能娶一个而不能娶两个
但是问题来了一个类只需要一个对象那在new对象的时候只new一次对象不就可以了么为什么还要弄个这么麻烦的东西呢
因为啊只new一次对象确实是只有一个但是呢如果你在写代码的过程中忘了呢然后又new了一次这种概率是很大的毕竟人是最不靠谱的动物就像是有一句话说的好宁可相信世界上有鬼也不要相信男人的那张嘴所以的为了防止这种失误发生就有了单例模式在Java中也有许多类似的机制比如final就会保证修饰的变量肯定是不能改变的override保证你这方法肯定是一个重写方法这些都是在语法方面进行了一些限制但是在语法方面对于单例并没有特定的语法所以这里就通过编程技巧来达到类似的限制效果
单例模式的两种实现方式
饿汉模式
1.在类中实例化类的对象给外界提供一个方法来使用这个对象;
2.将构造方法用private修饰保证在类外不能再实例化这个类对象
public class SingleTon {//在类的内部实例化对象public static SingleTon instance new SingleTon();//定义一个方法用来获取这个对象//后序如果类外的代码想要使用对象时直接调用这个方法即可public static SingleTon getInstance() {return instance;}//设置一个私有的构造方法保证在这个类外无法实例化这个对象private SingleTon(){}
}可以看到这里的对象被static修饰所以在类被加载的时候创建创建的时机就比较早并且被static修饰的对象只会被创建一次所以这种在类加载时就创建实例的模式称为饿汉模式
懒汉模式
懒汉模式单线程版
这样的写法与上面的相同点就是同样在类外不能再第二次实例化对象不同点是将创建对象的时机放在getInstance方法中这样在类加载的时候就不会创造实例而是当第一次调用这个方法时才会去创建
public class SingleTon {public static SingleTon instance null;//定义一个方法用来获取这个对象//后序如果类外的代码想要使用对象时直接调用这个方法即可public static SingleTon getInstance() {//懒汉模式if(instance null) {instance new SingleTon();}return instance;}//设置一个私有的构造方法保证在这个类外无法实例化这个对象private SingleTon(){}
}懒汉模式多线程版
在线程安全方面上面的饿汉模式是在多线程下是安全的而懒汉模式在多线程下是不安全的
因为如果多个线程同时访问一个变量那么不会出现不安全问题如果多个线程同时修改一个变量就有可能出现不安全问题
饿汉模式下只进行了访问没有涉及到修改 懒汉模式下不仅进行了访问还涉及了修改那么下面就讲解以下懒汉模式在多线程下如何会产生不安全 既然出现了不安全问题那么如何将懒汉模式修改成安全的呢
方法进行加锁使线程安全 但是如果锁加在这个地方仍然是不安全的因为这样还是会进行穿插执行如果两个并发的进入的 if 语句中那么就会进行锁竞争假设thread1 获取到了锁thread2 在阻塞等待等到 thread1 创建一次对象释放锁后thread2 就又会载获取到锁进行创建对象所以这个加锁操作并没有保证它是一个整体(非原子性) 所以说并不是加了锁就安全只有锁加对了才会安全在加锁的时候要保证以下几方面 锁的 {} 的范围是合理的能够把需要作为整体的每个部分都包括进去 锁的对象能够起到锁竞争的效果
懒汉模式多线程版改进
将if语句和new都放在锁里面成为一个整体这样就避免了会穿插执行 public static SingleTon getInstance() {synchronized (SingleTon.class) {if(instance null) {instance new SingleTon();}}return instance;}但是上述代码还有一个问题每当调用getInstance时都会尝试去进行加锁而加锁是一个开销很大的操作而懒汉模式之所以会出现线程不安全问题是因为只是在第一次调用getInstance方法new对象时可能会出现问题但是只要new完对象以后就不用再进行锁竞争了直接访问就可以了所以再次进行优化 public static SingleTon getInstance() {//在最外面在进行一次判断if(instance null) {synchronized (SingleTon.class) {if(instance null) {instance new SingleTon();}}}return instance;}在第一次实例化对象后以后再调用个getInstance方法时就不会再创建对象而且也不会再去获取锁因为第一个if判断语句都不会进去所以不会执行到加锁的语句
上面的单例模式看着好像是完全没问题了但是还是有一个问题就是可能会触发指令重排序问题所以就需要使用volatile解决指令重排序问题
volatile防止指令重排序
指令重排序编译器会保证在你代码逻辑不变的情况下对代码进行优化使代码的性能得到提高这样的操作称为指令重排序
举个例子 在代码中在实例化对象这一步可能会出现指令重排序问题下面就来讲解一下为什么 对于上述的指令重排序问题解决方案就是使用volatile关键字修饰singleTon
**线程安全的单例模式(懒汉模式)**
public class SingleTon {//使用volatile关键字修饰防止指令重排序public static volatile SingleTon singleTon null;public static SingleTon getSingleTon() {if(singleTon null) {synchronized (SingleTon.class) {if(singleTon null) {singleTon new SingleTon();}}}return singleTon;}private SingleTon() {};}这里再次提醒使用单例模式要注意三个要点
加锁两层if判断使用volatile修饰引用防止指令重排序