静态网站托管平台,品牌网站建设怎么收费,中国哪里正在大开发大建设,南充商城网站建设作者#xff1a;killianxu来源#xff1a;https://www.cnblogs.com/killianxu/p/11665903.htmljava内存模型知识导图一 并发问题及含义并发编程存在原子性、可见性、有序性问题。原子性即一系列操作要么都执行#xff0c;要么都不执行。 可见性,一个线程对共享变量的修改killianxu来源https://www.cnblogs.com/killianxu/p/11665903.htmljava内存模型知识导图一 并发问题及含义 并发编程存在原子性、可见性、有序性问题。 原子性即一系列操作要么都执行要么都不执行。 可见性,一个线程对共享变量的修改另一个线程可能不会马上看到。由于多核CPU每个CPU核都有高速缓存会缓存共享变量某个线程对共享变量的修改会改变高速缓存中的值但却不会马上写入内存。另一个线程读到的是另一个核缓存的共享变量的值出现缓存不一致问题。 有序性即程序执行的顺序按照代码的先后顺序执行。编译器和处理器会对指令进行重排以优化指令执行性能重排不会改变单线程执行结果但在多线程中可能会引起各种各样的问题。二 内存模型 为了保证共享内存的正确性(可见性、有序性、原子性)内存模型定义了共享内存系统中多线程程序读写操作行为的规范。内存模型解决并发问题主要采用两种方式限制处理器优化和使用内存屏障。 顺序一致性内存模型是一种理论参考模型,提供了极强的内存可见性保证,具有两大特性:一个线程的所有操作按照程序的顺序执行,而不能重排序。所有线程只能看到单一的执行顺序。每个操作都必须原子执行且立刻对其它线程可见。 顺序一致性内存模型禁止很多处理器和编译器重排,影响执行性能,处理器内存模型和JMM对顺序一致性内存模型进行放松,执行性能:处理器内存模型JMM顺序一致性内存模型,易编程性:处理器内存模型三 java内存模型 Java内存模型(Java Memory Model ,JMM)是一种符合内存模型规范的屏蔽了各种硬件和操作系统的访问差异的保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。 Java内存模型规定了所有的变量都存储在主内存中每条线程还有自己的工作内存线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝线程对变量的所有操作都必须在工作内存中进行而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。主内存和工作内存可类比成计算机内存模型中的主存和缓存的概念。3.1 java内存模型解决并发问题方法 原子性在java中,只有简单的读取、赋值(而且必须是将数字赋值给某个变量变量之间的相互赋值不是原子操作)才是原子操作。在32位平台下对64位数据的赋值是需要通过两个操作来完成不能保证其原子性。要实现更大范围操作的原子性可以通过synchronized和Lock来实现。由于synchronized和Lock保证任一时刻只有一个线程执行该代码块从而保证了原子性。 可见性Java提供了volatile关键字来保证可见性,当一个共享变量被volatile修饰时它会保证修改的值会立即被更新到主存当有其他线程需要读取时它会去内存中读取新值。通过synchronized和Lock也能够保证可见性synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。 JMM通过happens-before关系向程序员提供跨线程的内存可见性保证:程序次序规则一段代码在单线程中执行的结果是有序的。注意是执行结果因为虚拟机、处理器会对指令进行重排序(重排序后面会详细介绍)。虽然重排序了但是并不会影响程序的执行结果所以程序最终执行的结果与顺序执行的结果是一致的。故而这个规则只对单线程有效在多线程环境下无法保证正确性。锁定规则这个规则比较好理解无论是在单线程环境还是多线程环境一个锁处于被锁定状态那么必须先执行unlock操作后面才能进行lock操作。volatile变量规则这是一条比较重要的规则它标志着volatile保证了线程可见性。通俗点讲就是如果一个线程先去写一个volatile变量然后一个线程去读这个变量那么这个写操作一定是happens-before读操作的。传递规则提现了happens-before原则具有传递性即A happens-before B , B happens-before C那么A happens-before C线程启动规则假定线程A在执行过程中通过执行ThreadB.start()来启动线程B那么线程A对共享变量的修改在接下来线程B开始执行后确保对线程B可见。线程终结规则假定线程A在执行的过程中通过制定ThreadB.join()等待线程B终止那么线程B在终止之前对共享变量的修改在线程A等待返回后可见。 有序性,可以使用synchronized和volatile来保证多线程之间操作的有序性。实现方式有所区别volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作。3.2 java并发原语Java内存模型除了定义了一套规范还提供了一系列原语封装了底层实现后供开发者直接使用。3.2.1 volatile内存语义:当写一个volatile变量时JMM会把该线程对应的本地内存中的所有共享变量刷新到主内存。当读一个volatile变量JMM会把该线程对应的本地内存置为无效,线程接下来从主内存中读取共享变量。实现:编译器在生成字节码时会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。在每个volatile写操作前面插入一个StoreStore屏障。StoreStore屏障禁止上面的普通写和volatile写重排序,保障上面的普通写在volatile写之前刷新到主内存。在每个volatile写操作后面插入一个StoreLoad屏障。避免volatile写与后面可能有的volatile读/写重排序。在每个volatile读操作的后面插入一个LoadLoad屏障。禁止下面的普通读操作和上面的volatile读操作重排序在每个volatile读操作的后面插入一个LoadStore屏障。禁止下面的普通写操作和上面的volatile读操作重排序3.2.2 synchronized内存语义:当线程释放锁时JMM会把该线程对应的本地内存中的共享变量刷新到主内存中.当线程获取锁时JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量.实现:java对象头组成:Mark Word指向类的指针数组长度(只有数组对象才有)Mark Word用于加锁操作,结构如下:图3.1 java对象头Mark Word synchronized用的锁是存在Java对象头里,任何java对象都存在一个锁,JVM基于进入和退出Monitor对象来实现方法同步和代码块同步。代码块同步是使用monitorenter和monitorexit指令实现的monitorenter指令是在编译后插入到同步代码块的开始位置而monitorexit是插入到方法结束处和异常处。监视器锁(Monitor)本质依赖操作系统的Mutex Lock(互斥锁)来实现如果互斥量已经上锁调用线程会阻塞阻塞或唤醒一条线程都需要操作系统来帮忙完成这就需要从用户态转换到核心态中因此状态转换需要耗费很多的处理器时间。在jdk1.6中加入对锁的优化措施,锁一共有4种状态级别从低到高依次是无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。锁可以升级但不能降级。偏向锁: 当一个线程访问同步块并获取锁时会在对象头和栈帧中的锁记录里存储锁偏向的线程ID以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径因为轻量级锁的获取及释放依赖多次CAS原子指令而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。轻量级锁: 轻量级锁是为了在线程近乎交替执行同步块时提高性能。多个线程竞争锁若当前只有一个等待线程则可通过自旋稍微等待一下可能另一个线程很快就会释放锁。 但是当自旋超过一定的次数或者一个线程在持有锁一个在自旋又有第三个来访时轻量级锁膨胀为重量级锁。重量级锁: 重量级锁是通过对象内部的一个叫做监视器锁(monitor)来实现的监视器锁本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态这个成本非常高。其它锁优化措施:锁消除、锁粗化、自旋锁(忙循环,适用持有锁的线程很快释放锁)、自适应的自旋锁(自旋次数不固定,前一次在同一个锁上的自旋时间及锁的拥有者的状态决定)。3.2.3 final 写final域禁止把final域的写重排序到构造函数之外。对于引用类型:在构造函数内对final域引用对象的成员域的写入,与在构造函数外将这个被构造对象的引用赋值给引用变量,这两个操作不能重排序。防止对象构造完成,未被初始化的final域被访问(要达到此目的,还需确保被构造对象不能在构造函数中“逸出”)读final域禁止初次读一个对象的引用和随后初次读这个对象包含的final域之间的重排序。确保在读一个对象的final域前,一定会先读包含这个final域对象的引用,如果引用不为空,引用对象的final域已经被初始化过。实现:JMM禁止编译器把final域的写重排序到构造函数之外。编译器在final域的写之后,构造函数return之前,插入StoreStore屏障,禁止处理器把final域的写重排序到构造函数之外。编译器会在读final域前面插入StoreStore屏障。