zoukankan      html  css  js  c++  java
  • Java 并发系列之三:java 内存模型(JMM)

    1. 并发编程的挑战

     2. 并发编程需要解决的两大问题

     3. 线程通信机制

     4. 内存模型

     5. volatile

     6. synchronized

     7. CAS

     8. 锁的内存语义

     9. DCL 双重检查锁定

     10. final

     11. txt

      1 java 内存模型(JMM)
      2     并发编程的挑战
      3         线程上下文切换
      4             定义:CPU通过给每个线程分配CPU时间片来实现多线程机制,使得即使是单核处理器也支持多线程。CPU通过时间片分配算法来循环执行任务,任务从保存到再次加载的过程就是一次上下文切换
      5             多线程不一定快,当并发执行累加操作不超过百万次时速度比串行执行累加操作要慢。因为线程有创建和上下文切换开销
      6             测试上下文切换的次数和时长
      7                 使用Lmbench3可以测量上下文切换的时长
      8                 使用vmstat 可以测量上下文切换的次数CS(Content Switch)
      9             减少上下文切换
     10                 无锁并发编程
     11                     多线程竞争锁时,会引起上下文切换,不用锁,如将数据的ID按照Hash算法取摸分段,不同的线程处理不同段的数据
     12                 CAS算法
     13                     JAVA的Atomic包使用CAS算法来更新数据,而不需要加锁
     14                 使用最少线程
     15                     避免创建不需要的线程,减少线上大量WAITING的线程
     16                 协程
     17                     在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换
     18         死锁
     19             避免死锁的常用方法
     20                 避免一个线程同时获取多个锁
     21                 避免一个线程在锁内同时占用多个资源
     22                 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制
     23                 数据库锁,加锁解锁必须在一个数据库连接里,否则会出现解锁失败
     24             原因
     25                 互斥
     26                 不可剥夺
     27                 请求和保持
     28                 循环等待
     29             对应措施
     30                 将独占性资源改成虚拟资源
     31                 占有一个的同时还请求另一个锁,前一个释放
     32                 预先分配
     33                 有序分配
     34         资源限制的挑战
     35             资源限制了程序的执行速度,受限制于资源,仍然是在串行,并且额外增加了上下文切换和资源调度所需要的时间
     36             资源限制
     37                 硬件
     38                     带宽的上传下载速度
     39                     硬盘读写速度
     40                     CPU处理速度
     41                 软件
     42                     数据库的连接
     43                     socket连接数
     44             如何解决资源限制
     45                 硬件
     46                     集群
     47                 软件
     48                     资源池复用资源
     49             资源限制情况下如何并发编程
     50                 根据不同的资源限制调整程序的并发度
     51     并发编程需要解决的两大问题
     52         线程之间如何通信
     53         线程之间如何同步
     54             同步是指程序中用于控制不同线间操作发生相对顺序的机制
     55     线程通信机制
     56         内存共享
     57             隐式通信,显示同步
     58             java采用
     59                 JMM通过控制主内存和每个线程的本地内存之间的交互,来提供内存可见性保证
     60                      处理器:内存屏障
     61                     as-if-serial 语义保证单线程内程序的执行结果不被改变
     62                     happens-before 关系保证正确同步的多线程程序的执行结果不被改变
     63                     理论:顺序一致性
     64                 因为隐式通信,所以会遇到内存可见性问题(这里的内存是指堆内存——实例域、静态域和数组元素,不包括局部变量、方法定义参数和异常处理参数)
     65                 4种方式
     66                     1. A线程写volatile变量,随后B线程读这个volatile变量
     67                     2. A线程写volatile变量,随后B线程用CAS更新这个volatile变量
     68                     3. A线程用CAS更新这个volatile变量,随后B线程用CAS更新这个volatile变量
     69                     4. A线程用CAS更新这个volatile变量,随后B线程读这个volatile变量
     70         消息传递
     71             显示通信,隐式同步
     72     内存模型
     73         处理器内存模型
     74             硬件级的内存模型
     75             重排序
     76                 为了程序的性能,处理器、编译器都会对程序进行重排序处理,但是会产生内存可见性问题
     77                 背景
     78                     现代处理器使用写缓冲区临时保存向内存写入数据,避免由于处理器停顿下来等待向内存写入数据而产生的延迟,但仅对它所在的处理器可见,导致处理器对内存的读写顺序不一定与内存实际发生的读写顺序一致!因此允许对写-读操作进行重排序
     79                 问题
     80                     重排序在多线程环境下可能会导致数据不安全
     81                 3大类型
     82                     编译器优化的重排序
     83                         intra-thread semantics 在单线程环境下不能改变程序运行的结果的条件下才能重排序
     84                     指令级并行的重排序
     85                         存在数据依赖关系的不允许重排序
     86                     内存系统的重排序
     87                          因为处理器使用缓存和读写缓冲区,使得加载和存储操作看上去是乱序的
     88             处理器的重排序规则
     89                 常见的处理器都允许Store-Load重排序,因为使用了写缓冲区
     90                 常见的处理器都不允许对存在数据依赖的操作重排序,遵守数据依赖性,遵循as-if-serial语义
     91                 5大类
     92                     Load-Load
     93                     Load-Store
     94                     Store-Store
     95                     Store-Load
     96                     数据依赖
     97                         如果两个操作访问同一个变量,并且这两个操作中有一个为写操作,此时两个操作之间就存在数据依赖性
     98                         3大类
     99                             写后读
    100                             读后写
    101                             写后写
    102             处理器的内存屏障
    103                 内存屏障是一组处理器指令,用于实现对内存操作的顺序限制
    104                 为了保证内存可见性,Java编译器再生产指令序列的适当位置会插入内存屏障制定来禁止特定类型的处理器重排序
    105                 内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。
    106                 4大类内存屏障指令
    107                     LoadLoad Barriers
    108                     LoadStore Barriers
    109                     StoreStore Barriers
    110                     StoreLoad Barriers
    111                         全能型的屏障,同时具有其他三个屏障的效果,但执行该屏障开销会很大,因为Buffer Fully Flush
    112             x86处理器只会对写读作重排序,故只有一个屏障StoreLoad
    113             处理器遵循as-if-serial语义 ,单线程满足顺序一致性
    114                 不管怎么重排序(编译器和处理器为了提高并行度),单线程程序的执行结果不能改变。编译器,runtime和处理器都必须遵守as-if-serial语义
    115                 为了遵循协议,编译器和处理器不会对存在数据依赖关系的操作做重排序。
    116                 单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果,顾as-if-serial允许对控制依赖的操作重排序,但在多线程程序中,控制依赖操作重排序可能会改变程序运行结果
    117                 as-if-serial语义使得单线程程序员无须担心重排序会干扰他们,也无需担心内存可见性问题
    118         顺序一致性内存模型
    119             多线程环境下的理想化的理论参考模型,处理器的内存模型和编程语言的内存模型都会以顺序一致性内存模型作为参照
    120             为程序提供了极强的内存可见性保证
    121             数据竞争
    122                 定义
    123                     在一个线程中写一个变量,在另一个线程中读同一个变量,而且写和读没有通过同步来排序
    124                 当程序未正确同步时,就可能会存在数据竞争
    125                 如果程序是正确同步的,程序的 执行将具有顺序一致性
    126                 这里的同步是广义上的同步,包括常用同步原语的正确使用(Synchronized、volatile 、lock和 final127             特性
    128                 一个线程中的所有操作必须按照程序的顺序来执行
    129                 不管程序是否同步,所有程序都只能看到一个单一的操作执行顺序,每个操作都必须原子执行且立即对所有的线程可见
    130         JMM
    131             语言级的内存模型
    132             JMM的内存可见性保证3方面
    133                 基本方针:在不改变(正确同步的)程序执行结果的前提下,尽可能地为编译器和处理器的优化打开方便之门(重排序)
    134                 1. 单线程程序。编译器,runtime和处理器都必须遵守 as-if-serial 语义共同确保单线程程序的执行结果与该程序在顺序一致性模型中的执行结果相同
    135                 2. 正确同步的多线程程序。happens-before 保证正确同步的多线程程序,程序的执行结果与该程序在顺序一致性内存模型中执行结果相同,JMM通过限制OR禁止编译器和处理器的重排序来为程序员提供内存可见性保证。(内存屏障)
    136                 3. 未同步或未正确同步的多线程程序。JMM只提供最小安全性保障:保证线程读到的值不是无中生有的,要么是之前某个线程写入的值,要么是默认值(0,null, false),但并不保证线程读取到的值一定是正确的。
    137             happens-before
    138                 JMM中最核心的理论,保证跨线程的内存的可见性(可以在一个线程之内,也可以再不用线程之间)通过内存屏障实现
    139                 背景
    140                     JMM设计的两大核心目标
    141                         为程序员提供足够强的内存可见性保证
    142                         对编译器和处理器的约束尽可能放松(只要不改变程序的执行结果,编译器处理器怎么优化都行)首先保证正确性,然后再追求执行效率
    143                 简单定义
    144                     在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系
    145                 定义
    146                     JMM对程序员保证
    147                         如果一个操作happens-before另一个操作,那么第一个操作的执行结果对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前
    148                     JMM对编译器和处理器重排序的约束原则
    149                         两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则判定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。
    150                 原生java满足8大规则
    151                     1. 程序顺序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
    152                     2. 监视器锁规则:一个unLock操作先行发生于后面对同一个锁额lock操作;
    153                     3. volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
    154                     4. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
    155                     5. 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
    156                     6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
    157                     7. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
    158                     8. 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;
    159                 happens-before与JMM的关系
    160                     happens-before规则是JMM呈现给程序员的视图
    161                     一个happens-before规则对应于一个或者多个编译器和处理器重排序规则
    162                 happens-before VS as-if-serial
    163                     as-if-serial 语义保证单线程内程序的执行结果不被改变;程序顺序执行
    164                     happens-before 关系保证正确同步的多线程程序的执行结果不被改变;happens-before制定的顺序执行
    165         三者的区别和联系
    166             JMM VS 处理器内存模型 VS 顺序一致性
    167                 处理器内存模型(x86 TSO PSO)和语言内存模型(JAVA C++)都比顺序一致性内存模型弱,换来的是高效的执行性能。
    168                 常见的处理器内存模型比常用的语言内存模型弱
    169                 处理器的内存模型和语言内存模型都会以顺序一致性内存模型作为参照
    170             JMM VS 处理器内存模型
    171                 JMM屏蔽了不同处理器内存模型的差异,为程序员呈现了一个一致的内存模型
    172                 常见的处理器内存模型比JMM弱(限制少,都能重排序,追求性能),Java编译器在生成字节码时,会在执行指令序列的适当位置插入内存屏障来限制处理器的重排序
    173                 JMM在不同的处理器中需要插入的内存屏障的数量和种类也不同,从而展示了一个一致的内存模型
    174              JMM VS 顺序一致性
    175                 1. 顺序一致性模型保证单线程内的操作会按照程序的顺序执行,而JMM不保证(临界区的重排序,但是不允许临界区的代码溢出到临界区之外,那样会破坏监视器的语义)
    176                 2. 顺序一致性模型保证所有的线程只能看到一致的整体操作执行顺序,而JMM不保证(因为线程的本地缓存没有及时刷新到主内存中,其他线程不可见)
    177                 3. 顺序一致性模型保证对所有的内存读写操作都具有原子性,而JMM不保证对64位的long和double型变量的写操作具有原子性
    178                 4. as if serial 和 hapens-before语义 满足顺序一致性(程序的执行结果与该程序在顺序一致性模型中的执行结果相同)
    179      Java 并发机制的底层原理实现
    180         volatile
    181         synchronized
    182         原子操作
    183             处理器
    184                 使用总线锁保证原子性
    185                 使用缓存锁保证原子性
    186             JMM
    187 188                 CAS
    189     final
    190         2大重排序规则
    191             1. 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序
    192             2. 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。
    193         final语义在处理器中的实现
    194             写final域:编译器会在构造函数final域写之后,return之前插入StoreStore屏障
    195             读final域:编译器在读final域的操作前面插入一个LoadLoad屏障
    196             x86处理器只会对写读作重排序,故只有一个屏障StoreLoad,所以对final域的读写不会插入任何内存屏障
    197         禁止重排序保证了2点
    198             在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了,而普通域不具有这个保证
    199             在读一个对象的final域之前,一定会先读包含这个final域的对象的引用
    200         还需要的另一个保证
    201             在构造函数内部,不能让这个被构造函数的引用为其他线程可见,也就是对象引用不能在构造函数中溢出OR溢出
    202     DCL 双重检查锁定(Double-Checked Locking)
    203         在Java多线程中有时候需要采用延迟初始化来降低初始化类和创建对象的开销
    204         双重检查锁定是常见的延迟初始化技术,但是它是一个错误的用法
    205         历史
    206             1. 非线程安全:普通“懒汉式”单例模式的实现
    207             2. 同步方法:Synchronized,线程安全但性能开销大
    208             3. 采用DCL降低同步开销,两次判断为null可以提高性能,但是有重排序问题
    209             4. volatile实现线程安全的静态字段和实例字段的延迟初始化
    210             5.  基于类初始化的解决方法(静态内部类)实现的静态字段的延迟初始化
    211         问题根源
    212             1. 分配对象的内存空间;2.初始化对象;3.设置instance指向刚分配的内存地址;  其中2和3可能发生重排序
    213             解决方案
    214                 禁止重排序
    215                     基于volatile的解决方案
    216                         静态字段和实例字段都能实现延迟初始化
    217                 允许重排序
    218                     允许重排序,但不允许其他线程看到这个重排序。happens-before 监视器锁规则
    219                     基于类初始化的解决方案
    220                         利用classloader的机制来保证初始化instance时只有一个线程,JVM在类初始化阶段会获取一个初始化锁,这个锁可以同步多个线程对同一个类的初始化。并且每个线程至少获得一次锁来确保这个类已经被初始化了。
    221                         只能让静态字段实现延迟初始化
    222         单例模式
    223             立即加载/饿汉模式
    224                 静态实例初始化
    225 私有构造方法
    226 静态getInstance
    227             延迟加载/懒汉模式
    228                 静态实例声明
    229 私有构造方法
    230 静态getInstance{
    231    if(是null){ 
    232         初始化  
    233     }
    234 }
    235                 解决
    236                     声明synchronized关键字
    237                     尝试同步代码块
    238                     针对重要代码进行单独的同步
    239                     使用DCL双检查锁机制
    240                     使用静态内置类实现单例模式
    241                     序列化和反序列化的单例模式实现
    242                         readResolve()
    243                     使用static代码块来实现单例模式
    244                     使用enum枚举数据类型实现单例模式
    245     锁的内存语义
    246         锁的功能
    247             让临界区互斥执行
    248             锁的内存语义
    249         锁的释放与获取的内存语义
    250             释放锁的线程向获取同一个锁的线程发送消息
    251             线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存
    252             线程获取锁时,JMM会把该线程对应的本地内存置为无效,从而使被监视器保护的临界区代码必须从内存中读取共享变量
    253             volatile的写读与 锁的释放获取 有相同的内存效果:volatile写和锁的释放有相同的内存语义,volatile读和锁的获取有相同的内存语义。这也是增强volatile的原因,提供了一种比锁更轻量级的线程之间的通信机制。
    254         锁的释放与获取的内存语义两种实现
    255             1. 利用volatile 变量的 写读 锁具有的内存语义
    256             2. 利用CAS(AQS里面的一个调用本地的方法,调用处理器cmpxchg指令添加lock前缀)所附带的volatile读和volatile写的内存语义
    257         公平锁和非公平锁的内存语义
    258             公平锁和非公平锁释放时,最后都要写一个volatile变量state
    259             公平锁获取时,首先会去读volatile变量。
    260             非公平锁获取时,首先会用CAS更新volatile变量,CAS同时具有volatile读和volatile写的内存语义
    261         JUC 包的实现
    262             通用化的实现模型
    263                 1. 声明共享变量为volatile
    264                 2. 使用CAS的原子条件更新来实现线程之间的同步
    265                 3. 同时配合volatile的读写和CAS具有的volatile读写的内存语义来实现线程之间通信
    266                 4. 基础类基于123:AQS、非阻塞结构、原子变量类
    267                 5. 高层类基于4:Lock、同步器、阻塞队列、Executor、并发容器
    268         锁的升级与对比
    269             JAVA1.6中,锁一共有4中状态,级别从低到高依次是:无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态
    270             这几种状态会随着竞争情况的逐渐升级,锁可以升级但是不能降级,这种策略是为了提高获得锁和释放锁的效率
    271             无锁
    272             偏向锁
    273                 背景
    274                     大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。
    275                     偏向锁使用了一种等到竞争出现才释放锁的机制。当其他线程尝试竞争偏向锁时持有偏向所的线程才会释放锁。
    276                 重要操作
    277                     获得偏向锁
    278                         1. 一个线程访问同步块并获得锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID
    279                         2. 以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要看对象头Mark Word里面是否存储着指向当前线程的偏向锁,如果有,表示线程已经获得了锁,如果没有,下一步
    280                         3. 判断Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁),如果没有,使用CAS竞争锁; 如果有,则尝试使用CAS将对象头的偏向锁线程ID指向当前线程
    281                     撤销偏向锁
    282                         其他线程访问同步块,尝试获得偏向锁,失败,启动撤销偏向锁
    283                         1. 首先暂停持有偏向锁的线程
    284                         2. 解锁,将线程ID设为空
    285                         3. 恢复线程
    286                     关闭偏向锁
    287                         java 7里默认是启用的
    288                         关闭延迟
    289                             XX:BiasedLockingStartupDelay=0
    290                         关闭偏向锁,程序默认会进入轻量级锁
    291                             XX:UseBiasedLocking=false
    292                 应用
    293                     优点
    294                         加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级的差别
    295                     缺点
    296                         如果线程间存在锁竞争,会带来额外的锁撤销的消耗
    297                     使用场景
    298                         适用于只有一个线程访问同步块场景
    299             轻量级锁
    300                 注意,轻量级锁解锁失败,锁就会膨胀成为重量级锁,就不会恢复到轻量级锁状态,当线程处于这个状态,其他线程试图获取锁时,会被阻塞住,当持有所得线程释放锁之后会唤醒这些线程,被唤醒的线程会进行新一轮的夺锁之争。
    301                 重要操作
    302                     轻量级加锁
    303                         1. 访问同步块,分配空间并复制Mark Word到栈 Displaced Mark Word
    304                         2. CAS尝试将对象头中Mark Word替换为指向锁记录的指针,成功则获得锁,执行同步体;失败表示其他线程竞争锁,当前线程通过使用自旋来获取锁
    305                     轻量级解锁
    306                         1. 解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头
    307                         2. 如果成功,表示没有竞争发生,如果失败,表示当前锁存在竞争(因为别的线程在争夺锁),锁就会膨胀成为重量级锁(别的线程阻塞),释放锁并唤醒等待的线程
    308                 应用
    309                     优点
    310                         竞争的线程不会阻塞,提高了程序的响应速度
    311                     缺点
    312                         如果始终得不到锁竞争的线程,使用自旋会消耗CPU
    313                     使用场景
    314                         追求响应时间
    315                         同步块执行速度非常快
    316             重量级锁
    317                 应用
    318                     优点
    319                         线程竞争不使用自旋,不会消耗CPU
    320                     缺点
    321                         线程阻塞,响应时间缓慢
    322                     使用场景
    323                         追求吞吐量
    324                         同步块执行速度较长
    325     CAS
    326         Compare And Swap, 整个JUC体系最核心、最基础理论,Java中通过锁和CAS实现原子操作
    327         内存地址V,旧的预期值A,要更新的值B,当且仅当内存地址V中的值等于旧的预期值A时才会将内存值V得值修改为B,否则什么也不干
    328         native中存在四个参数
    329         JVM中的CAS操作利用了处理器提供的CMPXCHG指令实现的。
    330         缺陷
    331             ABA问题
    332                 检查不到值的变化,实际上却变化了
    333                 解决方案
    334                     变量前追加版本号版本号
    335                     AtomicStampedReference
    336                         这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子的方式将该引用和该标志的值设置为给定的更新值。
    337             循环时间太长
    338                 自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销
    339                 解决方法
    340                     JVM如果能支持处理器提供的pause指令,那么效率一定会提升
    341                     pause作用
    342                         1. 可以延迟流水线执行指令(depipeline),使CPU不会消耗过多的执行资源
    343                         2. 避免在退出循环的时候因内存顺序冲突而引起的CPU流水线被清空,从而提高CPU的执行效率
    344             只能保证一个共享变量原则操作
    345                 对多个共享变量操作时,CAS无法保证操作的原子性,需要用锁
    346                 解决方案
    347                     AtomicReference类保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作
    348         原子操作类Atomic
    349             java.util.concurrent.atomic里的原子操作类提供了线程安全地更新一个变量的方式
    350             4大类型13个原子操作类
    351                 基本类型类
    352                     AtomicBoolean
    353                     AtomicInteger
    354                     AtomicLong
    355                 数组
    356                     AtomicIntegerArray
    357                     AtomicLongArray
    358                     AtomicReferenceArray
    359                 引用
    360                     AtomicReference
    361                     AtomicReferenceFieldUpdater
    362                     AtomicMarkableReference
    363                 属性
    364                     AtomicIntegerFieldUpdater
    365                     AtomicLongFieldUpdater
    366                     AtomicStampedReference
    367             核心底层
    368                 CAS
    369                     Unsafe只提供了3中CAS方法
    370                         final native boolean compareAndSwapObject()
    371                         final native boolean compareAndSwapInt()
    372                         final native boolean compareAndSwapLong()
    373         CAS V.S. 锁
    374             JVM中除了偏向锁,其他锁(轻量级锁、互斥锁)的实现方式都用了循环CAS,即当一个线程想进入同步块的时候使用循环CAS的方式来获取锁,当它退出同步块的时候使用循环CAS释放锁。
    375     synchronized
    376         同步、重量级锁
    377         只有使用Synchronized线程处于阻塞,其他Lock, AQS, LockSupport等线程都是处于等待状态
    378         原理
    379             synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证变量的内存可见性。
    380         锁对象
    381             1. 普通同步方法锁,是当前实例对象
    382             2. 静态同步方法,锁是当前类的class对象
    383             3. 同步方法块,锁是括号里面的对象
    384             java中的每一个对象都可以作为锁
    385         实现机制
    386             Java对象头
    387                 synchronized用的锁是保存在Java对象头里的
    388                 包括两部分数据
    389                     Mark Word(标记字段)
    390                         Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据,它会根据对象的状态复用自己的存储空间
    391                         包括:哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳
    392                     Klass Pointer(类型指针)  
    393             monitor
    394                 java 中 Object和Class中都关联了一个Monitor,一个monitor只能被一个线程拥有
    395                 Owner 活动线程
    396                     初始时为NULL表示当前没有任何线程拥有该monitor record, 当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL
    397                 实现
    398                     同步代码块采用 monitorenter、monitorexit指令显示的同步
    399                     同步方法使用ACC_SYNCHRONIZED标记符隐式的实现
    400         锁优化
    401             自旋锁
    402                 该线程等待一段时间,不会被立即挂起,循环看持有锁的线程是否会很快释放锁
    403                 自旋数字难以控制(XX: preBlockSpin)
    404                 存在理论:线程的频繁挂起、唤醒负担较重,可以认为每个线程占有锁的时间很短,线程挂起再唤醒得不偿失。
    405                 缺点
    406                     自旋次数无法确定
    407             适应性自旋锁
    408                 自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定
    409                 自旋成功,则可以增加自旋次数,如果获取锁经常失败,那么自旋次数会减少
    410             锁消除
    411                 若不存在数据竞争的情况,JVM会消除锁机制
    412                 判断依据
    413                     变量逃逸
    414             锁粗化
    415                 将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。例如for循环内部获得锁
    416             轻量级锁
    417                 在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗
    418                 通过CAS(CompareandSwap),即比较并替换,来获取和释放锁
    419                 缺点
    420                     在多线程环境下,其运行效率比重量级锁还会慢
    421                 性能依据
    422                     对于绝大部分的锁,在整个生命周期内部都是不会存在竞争的
    423             偏向锁
    424                 为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径
    425                 主要尽可能避免不必要的CAS操作,如果竞争锁失败,则升级为轻量级锁
    426     volatile
    427         特性
    428             volatile可见性:对一个volatile的读,总可以看到任意线程对这个变量最终的写
    429             volatile原子性:volatile对单个读/写具有原子性(64位Long、Double),但是复合操作除外,例如 i++
    430             volatile有序性:编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
    431         内存语义
    432             当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新到主内存中
    433             当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主存中读取共享变量
    434             volatile的写-读与 锁的释放-获取 有相同的内存效果:volatile写和锁的释放有相同的内存语义,volatile读和锁的获取有相同的内存语义。这也是增强volatile的原因,提供了一种比锁更轻量级的线程之间的通信机制。
    435         重排序规则
    436             1. 当第二个操作是volatile写操作时,不管第一个操作是什么,都不能重排序
    437             2. 当第一个操作是volatile读操作时,不管第二个操作是什么,都不能重排序
    438             3. 当第一个操作是volatile写操作,第二个操作是volatile读操作,不能重排序
    439         内存语义的实现
    440             内存屏障
    441                 内存屏障是一组处理器指令,用于实现对内存操作的顺序限制
    442                 基于保守策略的JMM内存屏障插入策略
    443                     在每个volatile写操作的前面插入一个StoreStore屏障
    444                         保证之前的都能刷新到主存
    445                     在每个volatile写操作的后面插入一个StoreLoad屏障
    446                         保证先写后读,能提高效率
    447                     在每个volatile读操作的后面插入一个LoadLoad屏障
    448                     在每个volatile读操作的后面插入一个LoadStore屏障
    449                 实际执行时,只要不改变volatile写-读的内存语义,编译器可以根据具体情况省略不必要的屏障
    450                 x86处理器只会对写读作重排序,故只有一个屏障StoreLoad即可正确实现volatile写-读的内存语义
    451         操作系统语义
    452             主存、高速缓存(线程私有)缓存一致?
    453             解决方案
    454                 通过在总线加 Lock 锁的方式,有些是缓存锁定
    455                 通过缓存一致性协议 MESI协议(修改、独占、共享、无效)
    456             实现原则
    457                 Lock前缀指令会引起处理器缓存回写到内存
    458                 一个处理器的缓存回写到内存会导致其他处理器的缓存失效
    459         内存模型
    460             内存屏障 限制重排序
    461             happens-before中的volatile规则
    462         volatile VS synchronized
    463             volatile是轻量级的synchronized
    464             使用恰当,它比synchronized使用和执行成本更低,因为不会引起线程上下文的切换和调度
    View Code

     12. 参考网址

    1. 参考来源:http://cmsblogs.com/wp-content/resources/img/sike-juc.png
    2. 《Java并发编程的艺术》_方腾飞PDF 提取码:o9vr
    3. http://ifeve.com/the-art-of-java-concurrency-program-1/
    4. Java并发学习系列-绪论
    5. Java并发编程实战
    6. 死磕 Java 并发精品合集
  • 相关阅读:
    重构技巧 引入Null对象
    python yield
    todo
    Python 函数式编程学习
    Python 修饰器
    socket
    Exception、RuntimeException
    设计模式
    线程池
    VMware异常关闭后再次启动提示“以独占方式锁定此配置文件失败”!!!
  • 原文地址:https://www.cnblogs.com/haimishasha/p/11198326.html
Copyright © 2011-2022 走看看