当前位置: 首页 > wzjs >正文

网站空间数据海外网站推广优化专员

网站空间数据,海外网站推广优化专员,佛山网站制作网页,wordpress新建页面添加导航一、Java 内存模型(JMM) JMM 即 Java Memory Model,它定义了主存(所有线程都共享的数据)、工作内存(线程私有的数据)抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、 CPU 指令优…

一、Java 内存模型(JMM)

JMM 即 Java Memory Model,它定义了主存(所有线程都共享的数据)、工作内存(线程私有的数据)抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、 CPU 指令优化等。

JMM 体现在以下几个方面

  • 原子性 - 保证指令不会受到线程上下文切换的影响

  • 可见性 - 保证指令不会受 cpu 缓存的影响

  • 有序性 - 保证指令不会受 cpu 指令并行优化的影响

那么上面提到了主存和工作内存,那什么是主存什么又是工作内存呢

  1. 主存:就是所有线程都共享的数据,例如 静态成员变量 成员变量
  2. 工作内存:就是每个线程私有的数据,例如 局部变量

二、线程可见性问题

先来看一个体现线程可见性问题的典型案例:

public class VisibilityDemo {// 未使用volatile修饰的共享变量static boolean run = true;public static void main(String[] args) throws InterruptedException {Thread workThread = new Thread(() -> {// 线程持续运行的条件判断while (run) {// 空循环消耗CPU}});workThread.start();// 主线程休眠1毫秒Thread.sleep(1);System.out.println("准备停止工作线程");run = false; // 此处修改对工作线程不可见}
}

现象分析:程序执行后会陷入死循环,主线程对run变量的修改并未被工作线程感知。

那么为什么会出现的这样情况,我们来分析一下

1.初始状态, t 线程刚开始从主内存读取了 run 的值到工作内存

2.因为 t 线程要频繁从主内存中读取 run 的值,JIT 即时编译器会将 run 的值缓存至自己工作内存中的高速缓存中,减少对主存中 run 的访问,提高效率

3.1 秒之后,main 线程修改了 run 的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值

此时问题就很明显了一个线程对主存中的数据进行了修改,另外一个线程不可见,此时就会发生问题,解决方法可以使用volatile 关键字

三、volatile 关键字:解决可见性

3.1 volatile 的核心作用

volatile可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存,这样就解决了可见性的问题了。

public class VolatileDemo {// 使用volatile修饰共享变量,强制从主内存读取volatile static boolean run = true;public static void main(String[] args) throws InterruptedException {Thread workThread = new Thread(() -> {while (run) {// 空循环}});workThread.start();Thread.sleep(1);System.out.println("停止工作线程");run = false; // 此时修改对工作线程可见}
}

不使用volatile 关键字 使用synchronized 也是可以解决可见性的问题

public class SynchronizedDemo {static boolean run = true;static final Object LOCK = new Object();public static void main(String[] args) throws InterruptedException {Thread workThread = new Thread(() -> {while (true) {synchronized (LOCK) {if (!run) {break;}}}});workThread.start();Thread.sleep(1);System.out.println("停止工作线程");synchronized (LOCK) {run = false;}}
}

虽然synchronized 也可以解决可见性问题 但是volatile 在解决可见性方面,性能是好于synchronized 的 因为synchronized 需要创建Monitor锁对象是重量级锁

3.2 可见性 vs 原子性

前面例子体现的实际就是可见性,它保证的是在多个线程之间,一个线程对 volatile 变量的修改对另一个线程可见, 不能保证原子性,仅用在一个写线程,多个读线程的情况: 上个例子中,从字节码理解是这样的:

注意:一个变量加上volatile 修饰后不能保证原子性

getstatic run // 线程 t 获取 run true 
getstatic run // 线程 t 获取 run true 
getstatic run // 线程 t 获取 run true 
getstatic run // 线程 t 获取 run true 
putstatic run // 线程 main 修改 run 为 false, 仅此一次
getstatic run // 线程 t 获取 run false

比较一下之前我们将线程安全时举的例子:两个线程一个 i++ 一个 i-- ,volatile 只能保证getStatic()的时候,获取到的值是最新值但是,不能解决指令交错

// 假设i的初始值为0 
getstatic i // 线程2-获取静态变量i的值 线程内i=0 getstatic i // 线程1-获取静态变量i的值 线程内i=0 
iconst_1 // 线程1-准备常量1 
iadd // 线程1-自增 线程内i=1 
putstatic i // 线程1-将修改后的值存入静态变量i 静态变量i=1 iconst_1 // 线程2-准备常量1 
isub // 线程2-自减 线程内i=-1 
putstatic i // 线程2-将修改后的值存入静态变量i 静态变量i=-1
注意 synchronized 语句块既可以保证代码块的原子性,也同时保证代码块内变量的可见性。但缺点是synchronized 是属于重量级操作,性能相对更低

如果在前面示例的死循环中加入 System.out.println() 会发现即使不加 volatile 修饰符,线程 t 也能正确看到对 run 变量的修改了。

问题原因:

在 Java 里,线程之间的可见性问题往往是由 Java 内存模型(JMM)造成的。当变量未用 volatile 修饰时,每个线程可能会将变量的值缓存到自己的工作内存中,而非直接从主内存读取。这就可能导致一个线程对变量的修改,其他线程无法及时看到。

加入 System.out.println() 后,即便没有用 volatile 修饰 run 变量,线程 t 也能正确看到对 run 变量的修改,这主要是因为 System.out.println() 方法存在同步机制。

System.out 属于 PrintStream 类的实例,而 PrintStream 类的很多方法都被 synchronized关键字修饰。在 Java 中,synchronized 块或者方法在进入和退出时会有内存屏障的作用。内存屏障会强制刷新工作内存中的变量到主内存,并且从主内存中重新读取变量的值。

println()中的源码也可以看到,方法是被synchronized修饰的

那为什么被synchronized就可以解决这个问题呢?

因为当线程执行到System.out.println();时,会进入synchronized,进入synchronized后,线程会从主内存中读取共享变量的数据,在退出synchronized的时候,线程会把自己工作内存中的共享变量的值刷新到主内存,所以每次执行System.out.println()的时候,线程t都会从主内存中重写读取run变量的值,这样就可以看见main线程对run变量的修改

private void newLine() {try {synchronized (this) {ensureOpen();textOut.newLine();textOut.flushBuffer();charOut.flushBuffer();if (autoFlush)out.flush();}}catch (InterruptedIOException x) {Thread.currentThread().interrupt();}catch (IOException x) {trouble = true;}
}

四、有序性

JVM 会在不影响正确性的前提下,可以调整语句的执行顺序,思考下面一段代码

static int i; 
static int j; 
// 在某个线程内执行如下赋值操作 
i = ...;  
j = ...;

可以看到,至于是先执行 i 还是 先执行 j ,对最终的结果不会产生影响。所以,上面代码真正执行时,既可以是

i = ...; j = ...; 也可以是 j = ...; i = ...;

这种特性称之为指令重排』,多线程下『指令重排』会影响正确性。

4.1诡异的结果

I_Result 是一个对象,有一个属性 r1 用来保存结果,问,可能的结果有几种?会有三种情况

第一种:结果是1:线程1 先执行,这时 ready = false,所以进入 else 分支结果为 1

第二种:结果是4:线程2 执行到 ready = true,线程1 执行,这回进入 if 分支,结果为 4

第三种:结果是0:假设在执行actor2的时候,num = 2 和 ready = true 发生了指令重排序,此时如果ready = true 执行了,num=2还没有执行,这个时候执行actor1方法,那么这个时候因为ready是true了,所以if语句成立,但是此时num还是0,因此最终的结果是0

int num = 0;
boolean ready = false; 
// 线程1 执行此方法 
public void actor1(I_Result r) { if(ready) {  r.r1 = num + num; } else { r.r1 = 1; } 
} 
// 线程2 执行此方法 
public void actor2(I_Result r) {  num = 2; ready = true;  
}

解决方法

使用 volatile 禁用指令重排序即可

五、volatile 原理

volatile 的底层实现原理是内存屏障,Memory Barrier(Memory Fence)

  • 对 volatile 变量的写指令后会加入写屏障

  • 对 volatile 变量的读指令前会加入读屏障

5.1 保证可见性

写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中

而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据

5.2 保证有序性

写屏障除了保证可见性,还可以保证写屏障之前的代码不会出现指令重排,不会将写屏障之前代码排序到后面去

读屏障会保证在读屏障之后的代码不会被指令重排序,排序到读屏障前面去

还是那句话,volatile 不能解决指令交错:

  • 写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读跑到它前面去

  • 而有序性的保证也只是保证了本线程内相关代码不被重排序

5.3 double-checked locking 问题

以著名的 double-checked locking 单例模式为例

public final class Singleton { private Singleton() { } private static Singleton INSTANCE = null; public static Singleton getInstance() {  if(INSTANCE == null) { // t2 // 说明是首次访问会进入同步代码块,而之后的不会进入synchronized synchronized(Singleton.class) {           if (INSTANCE == null) { // t1             INSTANCE = new Singleton(); }  } } return INSTANCE; } 
}

以上的实现特点是:

  • 懒惰实例化 第一次使用的时候才创建,后续直接返回第一次创建的对象

  • 首次使用 getInstance() 才使用 synchronized 加锁,后续使用时无需加锁

  • 有隐含的,但很关键的一点:第一个 if 使用了 INSTANCE 变量,是在同步块之外

上述看似完美的代码但是在多线程环境下,上面的代码是有问题的,getInstance 方法对应的字节码为:

0: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton; 
3: ifnonnull 37 
6: ldc #3 // class cn/itcast/n5/Singleton 
8: dup 
9: astore_0 
10: monitorenter 
11: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton; 
14: ifnonnull 27 
17: new #3 // class cn/itcast/n5/Singleton 
20: dup 
21: invokespecial #4 // Method "<init>":()V 
24: putstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton; 
27: aload_0 
28: monitorexit 
29: goto 37 
32: astore_1 
33: aload_0 
34: monitorexit 
35: aload_1 
36: athrow 
37: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton; 
40: areturn

首先getstatic是在获取INSTANCE这个静态变量,ifnonull 就对应这个if(INSTANCE == null)代码,ifnonull 是在判断这个静态变量,是不是,不是null ,如果不是null就跳转到37行去执行, ldc是在获取当前对象的类对象即Singleton.class ,dup 就是拷贝一份这个类对象的引用地址 astore_0临时存储,用于将来的解锁

其中 主要关注 17-24行

17 new 表示创建对象,将对象引用入栈 // new Singleton

20 dup 表示复制一份对象引用 // 引用地址

21 invokespecial 表示利用一个对象引用,调用构造方法

24 putstatic 表示利用一个对象引用,赋值给 static INSTANCE

也许 jvm 会优化为:先执行 24,再执行 21。也就是发生了指令重排序,如果两个线程 t1,t2 按如下时间序列执行:

 

其实核心问题在于,INSTANCE = new Singleton(); 在这行代码,首先synchronized不能阻止指令的重排序,虽然synchronized可以解决 原子性 可见性 有序性,但是需要INSTANCE 这个对象完全的交给synchronized保护,在目前的案例中 就有一部分是在synchronized的外部 就比如 现在t1线程执行到了24行的把静态变量赋值的操作,但是还没有调用构造方法进行初始化,但是t2线程可以执行synchronized外部的代码,因为t1线程已经执行了赋值的操作,此时t2线程得到的就是一个未初始化的单例对象,正是因为t2线程在外部访问了共享变量,如果此时t1线程的内部发生了指令重排序,就会发生问题

关键在于 0: getstatic 这行代码在 monitor 控制之外,它就像之前举例中不守规则的人,可以越过 monitor 读取

INSTANCE 变量的值 ,这时 t1 还未完全将构造方法执行完毕,如果在构造方法中要执行很多初始化操作,那么 t2 拿到的是将是一个未初始化完毕的单例对象。

对 INSTANCE 使用 volatile 修饰即可,可以禁用指令重排,但要注意在 JDK 5 以上的版本的 volatile 才会真正有效

5.4 double-checked locking 解决

public final class Singleton { private Singleton() { } private static volatile Singleton INSTANCE = null; public static Singleton getInstance() {  if(INSTANCE == null) { // t2 // 首次访问会同步,而之后的使用没有 synchronized synchronized(Singleton.class) {           if (INSTANCE == null) { // t1             INSTANCE = new Singleton(); }  } } return INSTANCE; } 
}

当前核心的问题是在 21行的代码调用初始化的方法,被重排序到了24行给静态变量赋值,的后面去,现在加上了volatile关键字,在读操作的时候会加上一个读屏障,在写操作的时候会加上一个写屏障 读屏障防止后面的代码被指令重排序到前面去,写屏障防止上面的代码被指令重排序到后面去 现在加上了写屏障也就是说 21行的代码不会跑到24行的后面去,因此t2线程在读取的时候肯定会读取到初始化后的数据 即使t2线程在写之前就去读取,此时读取到的是null 这个时候第一个if判断就会成立,然后进入synchronized 此时就可以由synchronized保证线程安全性


文章转载自:

http://7X3TIcdu.qqbjt.cn
http://NEzCTClq.qqbjt.cn
http://W5faWUvk.qqbjt.cn
http://PYgsOyk5.qqbjt.cn
http://oLDKHjz3.qqbjt.cn
http://eIe9PIjh.qqbjt.cn
http://SLXb8Fsq.qqbjt.cn
http://Je3UPWVg.qqbjt.cn
http://IgXDMm4Z.qqbjt.cn
http://4d8cewJ5.qqbjt.cn
http://re1XhmAh.qqbjt.cn
http://2anbreaW.qqbjt.cn
http://Cg5OsQ1R.qqbjt.cn
http://EykziVLM.qqbjt.cn
http://TqYaxiTm.qqbjt.cn
http://dFQ81Vpr.qqbjt.cn
http://rtrLTiTg.qqbjt.cn
http://BVlTYvR8.qqbjt.cn
http://dKh2sSV5.qqbjt.cn
http://HgM5LiY6.qqbjt.cn
http://mftMYs5s.qqbjt.cn
http://Xq713ak5.qqbjt.cn
http://lEQjtHAZ.qqbjt.cn
http://znNvQRfL.qqbjt.cn
http://M9DMHUxQ.qqbjt.cn
http://5VKdgrRU.qqbjt.cn
http://3qTBrc6e.qqbjt.cn
http://1Z17vGte.qqbjt.cn
http://blgyu4Uc.qqbjt.cn
http://YxEQVJ2w.qqbjt.cn
http://www.dtcms.com/wzjs/770281.html

相关文章:

  • 网站空间 按流量计费巴彦淖尔专业做网站的
  • 在线建站系统网站排名第一
  • 公司做网站的费用怎么账务处理seo搜索引擎优化推广
  • 装饰公司营销型网站设计商标注册查询怎么查
  • 佛山网站优化运营建设通一年多少钱
  • 望野博物馆馆长阎焰google seo整站优化
  • 如何制作个人手机网站网站建设与运营的课程标准
  • 电影网站开发PPT模板做网站算新媒体运营吗
  • 网上书店电子商务网站建设绿色郑州网站
  • 力洋网站建设公司郑州网站建设哪家便宜
  • 网站制作上首页微信app制作
  • wordpress 导航栏登录搜索引擎排名优化的关键是
  • 如皋网站建设招标前端外包
  • 新网站建设方案ppt个人网站搭建模拟感想
  • 做网站平台赚钱吗厦门网站seo优化
  • rust做网站黄骅贴吧百度贴吧
  • 淘宝网站建设情况中卫网红大型蹦床设备
  • 西安烽盈网站建设推广光明做网站
  • 做茶叶网站烟台正规网站建设
  • 网站建设策划结束语设计网站推荐html代码
  • 最好的网站开发公司电话福州商城网站开发公司
  • 语言免费网站建设书店建设网站的能力
  • dede网站更新如何同步腾讯微博更新搭建网站的主要风险
  • 网站建设经费保障平价建网站
  • 山东省住房和城乡城乡建设厅网站wordpress角色内容
  • 中企动力做网站要全款简约个人网站
  • 怎么自己做个免费网站吗企业管理软件排名
  • 购物最便宜的app鹤壁网站seo优化
  • 网站搭建设计软文世界平台
  • 西安市高陵区建设局网站基础建设是什么意思