精华内容
下载资源
问答
  • volatile底层原理

    2020-03-27 16:36:21
    volatile实现原理:多处理器情况下,底层是通过JVM向处理器发送Lock前缀指令,Lock指令会让处理器将缓存回写到主存中去,并且使得其他处理器的缓存全部无效,也就是说,volatile可以读到主存里最新的值,即保证单个...

    volatile实现原理:多处理器情况下,底层是通过JVM向处理器发送Lock前缀指令,Lock指令会让处理器将缓存回写到主存中去,并且使得其他处理器的缓存全部无效,也就是说,volatile可以读到主存里最新的值,即保证单个变量的可见性;除此之外,JVM还会发送load addl指令,load addl指令禁止指令重排,建立内存屏障,保证了内存指令操作的有序性。

     关于可见性和原子性的看法:

    
    volatile int  i = 0;//多线程访问,无论哪个线程访问都能保证i = 0;这个是可见性。
    
    i ++;  
    //1、读i值;
    //2、计算 i+1;
    //3、将计算后的i值赋给i;
    //多线程情况下,第一步读的i值保证可见性和原子性;第二步和第三部,都无法保证原子性。

    参考:

    https://www.jianshu.com/p/ef8de88b1343

    https://www.cnblogs.com/dolphin0520/p/3920373.html

    展开全文
  • volatile底层原理详解

    2021-06-02 16:01:56
    volatile底层原理详解volatile底层原理详解1、volatile的作用1.1、volatile变量的可见性1.2、volatile变量的禁止指令重排序2、volatile的的底层实现2.1、volatile 有序性实现2.2、volatile 禁止重排序2.3、volatile ...

    1、volatile的作用

    并发编程中有3大重要特性,了解一下:

    • 原子性
      一个操作或者多个操作,要么全部执行成功,要么全部执行失败。满足原子性的操作,中途不可被中断。

    • 可见性
      多个线程共同访问共享变量时,某个线程修改了此变量,其他线程能立即看到修改后的值。

    • 有序性
      程序执行的顺序按照代码的先后顺序执行。(由于JMM模型中允许编译器和处理器为了效率,进行指令重排序的优化。指令重排序在单线程内表现为串行语义,在多线程中会表现为无序。那么多线程并发编程中,就要考虑如何在多线程环境下可以允许部分指令重排,又要保证有序性)

    synchronized关键字同时保证上述三种特性。

    • synchronized是同步锁,同步块内的代码相当于同一时刻单线程执行,故不存在原子性和指令重排序的问题
    • synchronized关键字的语义JMM有两个规定,保证其实现内存可见性:
    • 线程解锁前,必须把共享变量的最新值刷新到主内存中;
    • 线程加锁前,将清空工作内存中共享变量的值,从主内存中冲洗取值。
    • volatile关键字作用的是保证可见性和有序性,并不保证原子性。

    那么,volatile是如何保证可见性和有序性的?我们先进行基于JMM层面的实现基础,后面两章会进行底层原理的介绍。

    1.1、volatile变量的可见性

    Java虚拟机规范中定义了一种Java内存 模型(Java Memory Model,即JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果。Java内存模型的主要目标就是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的细节。

    JMM中规定所有的变量都存储在主内存(Main Memory)中,每条线程都有自己的工作内存(Work Memory),线程的工作内存中保存了该线程所使用的变量的从主内存中拷贝的副本。线程对于变量的读、写都必须在工作内存中进行,而不能直接读、写主内存中的变量。同时,本线程的工作内存的变量也无法被其他线程直接访问,必须通过主内存完成。
    在这里插入图片描述
    对于普通共享变量,线程A将变量修改后,体现在此线程的工作内存。在尚未同步到主内存时,若线程B使用此变量,从主内存中获取到的是修改前的值,便发生了共享变量值的不一致,也就是出现了线程的可见性问题。

    volatile定义:

    • 当对volatile变量执行写操作后,JMM会把工作内存中的最新变量值强制刷新到主内存
    • 写操作会导致其他线程中的缓存无效
      这样,其他线程使用缓存时,发现本地工作内存中此变量无效,便从主内存中获取,这样获取到的变量便是最新的值,实现了线程的可见性。

    1.2、volatile变量的禁止指令重排序

    volatile是通过编译器在生成字节码时,在指令序列中添加“内存屏障”来禁止指令重排序的。

    硬件层面的“内存屏障”:

    • sfence:即写屏障(Store Barrier),在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存,以保证写入的数据立刻对其他线程可见
    • lfence:即读屏障(Load Barrier),在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据,以保证读取的是最新的数据。
    • mfence:即全能屏障(modify/mix Barrier ),兼具sfence和lfence的功能
    • lock 前缀:lock不是内存屏障,而是一种锁。执行时会锁住内存子系统来确保执行顺序,甚至跨多个CPU。

    JMM层面的“内存屏障”:

    • LoadLoad屏障: 对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
    • StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
    • LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
    • StoreLoad屏障: 对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。

    JVM的实现会在volatile读写前后均加上内存屏障,在一定程度上保证有序性。如下所示:


    • LoadLoadBarrier
      volatile 读操作
      LoadStoreBarrier

    • StoreStoreBarrier
      volatile 写操作
      StoreLoadBarrier

    2、volatile的的底层实现

    2.1、volatile 有序性实现

    volatile 的 happens-before 关系

    happens-before 规则中有一条是 volatile 变量规则:对一个 volatile 域的写,happens-before 于任意后续对这个 volatile 域的读。

    //假设线程A执行writer方法,线程B执行reader方法
    class VolatileExample {
        int a = 0;
        volatile boolean flag = false;
        
        public void writer() {
            a = 1;              // 1 线程A修改共享变量
            flag = true;        // 2 线程A写volatile变量
        } 
        
        public void reader() {
            if (flag) {         // 3 线程B读同一个volatile变量
            int i = a;          // 4 线程B读共享变量
            ……
            }
        }
    }
    
    

    根据 happens-before 规则,上面过程会建立 3 类 happens-before 关系。

    • 根据程序次序规则:1 happens-before 2 且 3 happens-before 4。
    • 根据 volatile 规则:2 happens-before 3。
    • 根据 happens-before 的传递性规则:1 happens-before 4。
      在这里插入图片描述

    2.2、volatile 禁止重排序

    • 为了性能优化,JMM 在不改变正确语义的前提下,会允许编译器和处理器对指令序列进行重排序。
    • JMM 提供了内存屏障阻止这种重排序。
    • Java 编译器会在生成指令系列时在适当的位置会插入内存屏障指令来禁止特定类型的处理器重排序。

    JMM 会针对编译器制定 volatile 重排序规则表。
    在这里插入图片描述

    1. " NO " 表示禁止重排序。
    2. 为了实现 volatile 内存语义时,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
    3. 对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎是不可能的,为此,JMM 采取了保守的策略。
      • 在每个 volatile 写操作的前面插入一个 StoreStore 屏障。
      • 在每个 volatile 写操作的后面插入一个 StoreLoad 屏障。
      • 在每个 volatile 读操作的后面插入一个 LoadLoad 屏障。 在每个 volatile 读操作的后面插入一个 LoadStore 屏障。
      • 在这里插入图片描述
        在这里插入图片描述

    2.3、volatile 的应用场景

    使用 volatile 必须具备的条件

    • 对变量的写操作不依赖于当前值。
    • 该变量没有包含在具有其他变量的不变式中。
    • 只有在状态真正独立于程序内其他内容时才能使用 volatile。
    展开全文
  • 主要介绍了通过实例解析JMM和Volatile底层原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • synchronized和volatile底层原理分析

    万次阅读 2020-04-27 14:16:23
    --------->| (COOPs && CMS free block) synchronized的横切面详解 synchronized原理 升级过程 汇编实现 vs reentrantLock的区别 java源码层级 synchronized(o) 字节码层级 monitorenter moniterexit JVM层级...

    CAS

    Compare And Swap (Compare And Exchange) / 自旋 / 自旋锁 / 无锁

    因为经常配合循环操作,直到完成为止,所以泛指一类操作

    cas(v, a, b) ,变量v,期待值a, 修改值b

    ABA问题,你的女朋友在离开你的这段儿时间经历了别的人,自旋就是你空转等待,一直等到她接纳你为止

    解决办法(版本号 AtomicStampedReference),基础类型简单值不需要版本号

    Unsafe

    AtomicInteger:

    public final int incrementAndGet() {
            for (;;) {
                int current = get();
                int next = current + 1;
                if (compareAndSet(current, next))
                    return next;
            }
        }
    
    public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }
    

    Unsafe:

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
    

    运用:

    package com.mashibing.jol;
    
    import sun.misc.Unsafe;
    
    import java.lang.reflect.Field;
    
    public class T02_TestUnsafe {
    
        int i = 0;
        private static T02_TestUnsafe t = new T02_TestUnsafe();
    
        public static void main(String[] args) throws Exception {
            //Unsafe unsafe = Unsafe.getUnsafe();
    
            Field unsafeField = Unsafe.class.getDeclaredFields()[0];
            unsafeField.setAccessible(true);
            Unsafe unsafe = (Unsafe) unsafeField.get(null);
    
            Field f = T02_TestUnsafe.class.getDeclaredField("i");
            long offset = unsafe.objectFieldOffset(f);
            System.out.println(offset);
    
            boolean success = unsafe.compareAndSwapInt(t, offset, 0, 1);
            System.out.println(success);
            System.out.println(t.i);
            //unsafe.compareAndSwapInt()
        }
    }
    

    jdk8u: unsafe.cpp:

    cmpxchg = compare and exchange

    UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
      UnsafeWrapper("Unsafe_CompareAndSwapInt");
      oop p = JNIHandles::resolve(obj);
      jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
      return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
    UNSAFE_END
    

    jdk8u: atomic_linux_x86.inline.hpp

    is_MP = Multi Processor

    inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
      int mp = os::is_MP();
      __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
                        : "=a" (exchange_value)
                        : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                        : "cc", "memory");
      return exchange_value;
    }
    

    jdk8u: os.hpp is_MP()

      static inline bool is_MP() {
        // During bootstrap if _processor_count is not yet initialized
        // we claim to be MP as that is safest. If any platform has a
        // stub generator that might be triggered in this phase and for
        // which being declared MP when in fact not, is a problem - then
        // the bootstrap routine for the stub generator needs to check
        // the processor count directly and leave the bootstrap routine
        // in place until called after initialization has ocurred.
        return (_processor_count != 1) || AssumeMP;
      }
    

    jdk8u: atomic_linux_x86.inline.hpp

    #define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "
    

    最终实现:

    cmpxchg = cas修改变量值

    lock cmpxchg 指令
    

    硬件:

    lock指令在执行后面指令的时候锁定一个北桥信号

    (不采用锁总线的方式)

    markword

    工具:JOL = Java Object Layout

    <dependencies>
            <!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
            <dependency>
                <groupId>org.openjdk.jol</groupId>
                <artifactId>jol-core</artifactId>
                <version>0.9</version>
            </dependency>
        </dependencies>
    

    jdk8u: markOop.hpp

    // Bit-format of an object header (most significant first, big endian layout below):
    //
    //  32 bits:
    //  --------
    //             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
    //             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
    //             size:32 ------------------------------------------>| (CMS free block)
    //             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
    //
    //  64 bits:
    //  --------
    //  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
    //  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
    //  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
    //  size:64 ----------------------------------------------------->| (CMS free block)
    //
    //  unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)
    //  JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)
    //  narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
    //  unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
    

    synchronized的横切面详解

    1. synchronized原理
    2. 升级过程
    3. 汇编实现
    4. vs reentrantLock的区别

    java源码层级

    synchronized(o)

    字节码层级

    monitorenter moniterexit

    JVM层级(Hotspot)

    package com.mashibing.insidesync;
    
    import org.openjdk.jol.info.ClassLayout;
    
    public class T01_Sync1 {
      
    
        public static void main(String[] args) {
            Object o = new Object();
    
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
    }
    
    com.mashibing.insidesync.T01_Sync1$Lock object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4   (object header)  05 00 00 00 (00000101 00000000 00000000 00000000) (5)
          4     4   (object header)  00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4   (object header)  49 ce 00 20 (01001001 11001110 00000000 00100000) (536923721)
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    
    com.mashibing.insidesync.T02_Sync2$Lock object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4   (object header)  05 90 2e 1e (00000101 10010000 00101110 00011110) (506368005)
          4     4   (object header)  1b 02 00 00 (00011011 00000010 00000000 00000000) (539)
          8     4   (object header)  49 ce 00 20 (01001001 11001110 00000000 00100000) (536923721)
         12     4        (loss due to the next object alignment)
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 4 bytes external = 4 bytes tota
    

    InterpreterRuntime:: monitorenter方法

    IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
    #ifdef ASSERT
      thread->last_frame().interpreter_frame_verify_monitor(elem);
    #endif
      if (PrintBiasedLockingStatistics) {
        Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
      }
      Handle h_obj(thread, elem->obj());
      assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
             "must be NULL or an object");
      if (UseBiasedLocking) {
        // Retry fast entry if bias is revoked to avoid unnecessary inflation
        ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
      } else {
        ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
      }
      assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
             "must be NULL or an object");
    #ifdef ASSERT
      thread->last_frame().interpreter_frame_verify_monitor(elem);
    #endif
    IRT_END
    

    synchronizer.cpp

    revoke_and_rebias

    void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
     if (UseBiasedLocking) {
        if (!SafepointSynchronize::is_at_safepoint()) {
          BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
          if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
            return;
          }
        } else {
          assert(!attempt_rebias, "can not rebias toward VM thread");
          BiasedLocking::revoke_at_safepoint(obj);
        }
        assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
     }
    
     slow_enter (obj, lock, THREAD) ;
    }
    
    void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
      markOop mark = obj->mark();
      assert(!mark->has_bias_pattern(), "should not see bias pattern here");
    
      if (mark->is_neutral()) {
        // Anticipate successful CAS -- the ST of the displaced mark must
        // be visible <= the ST performed by the CAS.
        lock->set_displaced_header(mark);
        if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
          TEVENT (slow_enter: release stacklock) ;
          return ;
        }
        // Fall through to inflate() ...
      } else
      if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
        assert(lock != mark->locker(), "must not re-lock the same lock");
        assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
        lock->set_displaced_header(NULL);
        return;
      }
    
    #if 0
      // The following optimization isn't particularly useful.
      if (mark->has_monitor() && mark->monitor()->is_entered(THREAD)) {
        lock->set_displaced_header (NULL) ;
        return ;
      }
    #endif
    
      // The object header will never be displaced to this lock,
      // so it does not matter what the value is, except that it
      // must be non-zero to avoid looking like a re-entrant lock,
      // and must not look locked either.
      lock->set_displaced_header(markOopDesc::unused_mark());
      ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
    }
    

    inflate方法:膨胀为重量级锁

    锁升级过程

    JDK8:

    在这里插入图片描述

    无锁 - 偏向锁 - 轻量级锁 (自旋锁,自适应自旋)- 重量级锁

    synchronized优化的过程和markword息息相关

    用markword中最低的三位代表锁状态 其中1位是偏向锁位 两位是普通锁位

    1. Object o = new Object()
      锁 = 0 01 无锁态

    2. o.hashCode()
      001 + hashcode

      00000001 10101101 00110100 00110110
      01011001 00000000 00000000 00000000
      

      little endian big endian

      00000000 00000000 00000000 01011001 00110110 00110100 10101101 00000000

    3. 默认synchronized(o)
      00 -> 轻量级锁
      默认情况 偏向锁有个时延,默认是4秒
      why? 因为JVM虚拟机自己有一些默认启动的线程,里面有好多sync代码,这些sync代码启动时就知道肯定会有竞争,如果使用偏向锁,就会造成偏向锁不断的进行锁撤销和锁升级的操作,效率较低。

      -XX:BiasedLockingStartupDelay=0
      
    4. 如果设定上述参数
      new Object () - > 101 偏向锁 ->线程ID为0 -> Anonymous BiasedLock
      打开偏向锁,new出来的对象,默认就是一个可偏向匿名对象101

    5. 如果有线程上锁
      上偏向锁,指的就是,把markword的线程ID改为自己线程ID的过程
      偏向锁不可重偏向 批量偏向 批量撤销

    6. 如果有线程竞争
      撤销偏向锁,升级轻量级锁
      线程在自己的线程栈生成LockRecord ,用CAS操作将markword设置为指向自己这个线程的LR的指针,设置成功者得到锁

    7. 如果竞争加剧
      竞争加剧:有线程超过10次自旋, -XX:PreBlockSpin, 或者自旋线程数超过CPU核数的一半, 1.6之后,加入自适应自旋 Adapative Self Spinning , JVM自己控制
      升级重量级锁:-> 向操作系统申请资源,linux mutex , CPU从3级-0级系统调用,线程挂起,进入等待队列,等待操作系统的调度,然后再映射回用户空间

    (以上实验环境是JDK11,打开就是偏向锁,而JDK8默认对象头是无锁)

    偏向锁默认是打开的,但是有一个时延,如果要观察到偏向锁,应该设定参数

    没错,我就是厕所所长

    加锁,指的是锁定对象

    锁升级的过程

    JDK较早的版本 OS的资源 互斥量 用户态 -> 内核态的转换 重量级 效率比较低

    现代版本进行了优化

    无锁 - 偏向锁 -轻量级锁(自旋锁)-重量级锁

    偏向锁 - markword 上记录当前线程指针,下次同一个线程加锁的时候,不需要争用,只需要判断线程指针是否同一个,所以,偏向锁,偏向加锁的第一个线程 。hashCode备份在线程栈上 线程销毁,锁降级为无锁

    有争用 - 锁升级为轻量级锁 - 每个线程有自己的LockRecord在自己的线程栈上,用CAS去争用markword的LR的指针,指针指向哪个线程的LR,哪个线程就拥有锁

    自旋超过10次,升级为重量级锁 - 如果太多线程自旋 CPU消耗过大,不如升级为重量级锁,进入等待队列(不消耗CPU)-XX:PreBlockSpin

    自旋锁在 JDK1.4.2 中引入,使用 -XX:+UseSpinning 来开启。JDK 6 中变为默认开启,并且引入了自适应的自旋锁(适应性自旋锁)。

    自适应自旋锁意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。

    偏向锁由于有锁撤销的过程revoke,会消耗系统资源,所以,在锁争用特别激烈的时候,用偏向锁未必效率高。还不如直接使用轻量级锁。

    synchronized最底层实现

    
    public class T {
        static volatile int i = 0;
        
        public static void n() { i++; }
        
        public static synchronized void m() {}
        
        publics static void main(String[] args) {
            for(int j=0; j<1000_000; j++) {
                m();
                n();
            }
        }
    }
    
    

    java -XX:+UnlockDiagonositicVMOptions -XX:+PrintAssembly T

    C1 Compile Level 1 (一级优化)

    C2 Compile Level 2 (二级优化)

    找到m() n()方法的汇编码,会看到 lock comxchg …指令

    synchronized vs Lock (CAS)

     在高争用 高耗时的环境下synchronized效率更高
     在低争用 低耗时的环境下CAS效率更高
     synchronized到重量级之后是等待队列(不消耗CPU)
     CAS(等待期间消耗CPU)
     
     一切以实测为准
    

    锁消除 lock eliminate

    public void add(String str1,String str2){
             StringBuffer sb = new StringBuffer();
             sb.append(str1).append(str2);
    }
    

    我们都知道 StringBuffer 是线程安全的,因为它的关键方法都是被 synchronized 修饰过的,但我们看上面这段代码,我们会发现,sb 这个引用只会在 add 方法中使用,不可能被其它线程引用(因为是局部变量,栈私有),因此 sb 是不可能共享的资源,JVM 会自动消除 StringBuffer 对象内部的锁。

    锁粗化 lock coarsening

    public String test(String str){
           
           int i = 0;
           StringBuffer sb = new StringBuffer():
           while(i < 100){
               sb.append(str);
               i++;
           }
           return sb.toString():
    }
    

    JVM 会检测到这样一连串的操作都对同一个对象加锁(while 循环内 100 次执行 append,没有锁粗化的就要进行 100 次加锁/解锁),此时 JVM 就会将加锁的范围粗化到这一连串的操作的外部(比如 while 虚幻体外),使得这一连串操作只需要加一次锁即可。

    锁降级(不重要)

    https://www.zhihu.com/question/63859501

    其实,只被VMThread访问,降级也就没啥意义了。所以可以简单认为锁降级不存在!

    超线程

    一个ALU + 两组Registers + PC

    参考资料

    http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

    volatile的用途

    1.线程可见性

    package com.mashibing.testvolatile;
    
    public class T01_ThreadVisibility {
        private static volatile boolean flag = true;
    
        public static void main(String[] args) throws InterruptedException {
            new Thread(()-> {
                while (flag) {
                    //do sth
                }
                System.out.println("end");
            }, "server").start();
    
    
            Thread.sleep(1000);
    
            flag = false;
        }
    }
    

    2.防止指令重排序

    问题:DCL单例需不需要加volatile?

    CPU的基础知识

    • 缓存行对齐
      缓存行64个字节是CPU同步的基本单位,缓存行隔离会比伪共享效率要高
      Disruptor

      package com.mashibing.juc.c_028_FalseSharing;
      
      public class T02_CacheLinePadding {
          private static class Padding {
              public volatile long p1, p2, p3, p4, p5, p6, p7; //
          }
      
          private static class T extends Padding {
              public volatile long x = 0L;
          }
      
          public static T[] arr = new T[2];
      
          static {
              arr[0] = new T();
              arr[1] = new T();
          }
      
          public static void main(String[] args) throws Exception {
              Thread t1 = new Thread(()->{
                  for (long i = 0; i < 1000_0000L; i++) {
                      arr[0].x = i;
                  }
              });
      
              Thread t2 = new Thread(()->{
                  for (long i = 0; i < 1000_0000L; i++) {
                      arr[1].x = i;
                  }
              });
      
              final long start = System.nanoTime();
              t1.start();
              t2.start();
              t1.join();
              t2.join();
              System.out.println((System.nanoTime() - start)/100_0000);
          }
      }
      
      

      MESI

    • 伪共享

    • 合并写
      CPU内部的4个字节的Buffer

      package com.mashibing.juc.c_029_WriteCombining;
      
      public final class WriteCombining {
      
          private static final int ITERATIONS = Integer.MAX_VALUE;
          private static final int ITEMS = 1 << 24;
          private static final int MASK = ITEMS - 1;
      
          private static final byte[] arrayA = new byte[ITEMS];
          private static final byte[] arrayB = new byte[ITEMS];
          private static final byte[] arrayC = new byte[ITEMS];
          private static final byte[] arrayD = new byte[ITEMS];
          private static final byte[] arrayE = new byte[ITEMS];
          private static final byte[] arrayF = new byte[ITEMS];
      
          public static void main(final String[] args) {
      
              for (int i = 1; i <= 3; i++) {
                  System.out.println(i + " SingleLoop duration (ns) = " + runCaseOne());
                  System.out.println(i + " SplitLoop  duration (ns) = " + runCaseTwo());
              }
          }
      
          public static long runCaseOne() {
              long start = System.nanoTime();
              int i = ITERATIONS;
      
              while (--i != 0) {
                  int slot = i & MASK;
                  byte b = (byte) i;
                  arrayA[slot] = b;
                  arrayB[slot] = b;
                  arrayC[slot] = b;
                  arrayD[slot] = b;
                  arrayE[slot] = b;
                  arrayF[slot] = b;
              }
              return System.nanoTime() - start;
          }
      
          public static long runCaseTwo() {
              long start = System.nanoTime();
              int i = ITERATIONS;
              while (--i != 0) {
                  int slot = i & MASK;
                  byte b = (byte) i;
                  arrayA[slot] = b;
                  arrayB[slot] = b;
                  arrayC[slot] = b;
              }
              i = ITERATIONS;
              while (--i != 0) {
                  int slot = i & MASK;
                  byte b = (byte) i;
                  arrayD[slot] = b;
                  arrayE[slot] = b;
                  arrayF[slot] = b;
              }
              return System.nanoTime() - start;
          }
      }
      
      
    • 指令重排序

      package com.mashibing.jvm.c3_jmm;
      
      public class T04_Disorder {
          private static int x = 0, y = 0;
          private static int a = 0, b =0;
      
          public static void main(String[] args) throws InterruptedException {
              int i = 0;
              for(;;) {
                  i++;
                  x = 0; y = 0;
                  a = 0; b = 0;
                  Thread one = new Thread(new Runnable() {
                      public void run() {
                          //由于线程one先启动,下面这句话让它等一等线程two. 读着可根据自己电脑的实际性能适当调整等待时间.
                          //shortWait(100000);
                          a = 1;
                          x = b;
                      }
                  });
      
                  Thread other = new Thread(new Runnable() {
                      public void run() {
                          b = 1;
                          y = a;
                      }
                  });
                  one.start();other.start();
                  one.join();other.join();
                  String result = "第" + i + "次 (" + x + "," + y + ")";
                  if(x == 0 && y == 0) {
                      System.err.println(result);
                      break;
                  } else {
                      //System.out.println(result);
                  }
              }
          }
      
      
          public static void shortWait(long interval){
              long start = System.nanoTime();
              long end;
              do{
                  end = System.nanoTime();
              }while(start + interval >= end);
          }
      }
      

    volatile如何解决指令重排序

    1: volatile i

    2: ACC_VOLATILE

    3: JVM的内存屏障

    4:hotspot实现

    bytecodeinterpreter.cpp

    int field_offset = cache->f2_as_index();
              if (cache->is_volatile()) {
                if (support_IRIW_for_not_multiple_copy_atomic_cpu) {
                  OrderAccess::fence();
                }
    

    orderaccess_linux_x86.inline.hpp

    inline void OrderAccess::fence() {
      if (os::is_MP()) {
        // always use locked addl since mfence is sometimes expensive
    #ifdef AMD64
        __asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
    #else
        __asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
    #endif
      }
    }
    

    LOCK 用于在多处理器中执行指令时对共享内存的独占使用。
    它的作用是能够将当前处理器对应缓存的内容刷新到内存,并使其他处理器对应的缓存失效。
    另外还提供了有序的指令无法越过这个内存屏障的作用。

    展开全文
  • Volatile底层原理剖析

    2021-02-16 08:23:54
    Volatile底层原理剖析JMMVolatile缓存可见性实现原理指令重排 JMM Volatile缓存可见性实现原理 理解:lock前缀指令会锁定这块内存区域的缓存,当执行引擎修改将数据修改后并assign赋值到线程的工作内存时,会被...

    JMM

    在这里插入图片描述
    在这里插入图片描述

    Volatile缓存可见性实现原理

    在这里插入图片描述
    理解:lock前缀指令会锁定这块内存区域的缓存,当执行引擎修改将数据修改后并assign赋值到线程的工作内存时,会被迅速的加载到共享内存,即使修改数据后还有其他的业务,不用等待加载其他业务就直接将数据传回主内存,主要是一个时效性。在加载到主内存的过程中会经过主线(缓存一致性),其他线程监听着主线,如果监听到感兴趣的数据写回共享内存操作就会引起数据无效协议,即其他线程重新加载主存中的数据。

    指令重排

    我们知道为了提高程序执行的性能,编译器和执行器(处理器)通常会对指令做一些优化(重排序)。volatile通过内存屏障实现了防止指令重排的目的。同时lock前缀指令相当于一个内存屏障,它会告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。

    在每个volatile写操作的前面插入一个StoreStore屏障。
    在每个volatile写操作的后面插入一个StoreLoad屏障。
    在每个volatile读操作的后面插入一个LoadLoad屏障。
    在每个volatile读操作的后面插入一个LoadStore屏障。

    展开全文
  • volatile底层原理分析

    2020-02-05 14:42:50
    文章目录一、CPU多核缓存架构模型1.1、早期的计算机CPU架构模型1.2、现在的计算机CPU多核缓存架构模型1.2.1、内存间交互操作1.2.2、...Java内存模型)2.1、JMM存在的问题三、volatile底层原理3.1、并发编程的三大特...
  • volatile 底层原理以及特性详解

    万次阅读 2020-06-09 20:40:22
    2 当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。 3 volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,
  • 讲透volatile底层原理volatile支持原子性volatile支持可见性volatile支持有序性volatile常见使用场景volatile与锁的区别 在上文中说了,在并发编程中可能出现原子性、可见性和有序性的问题。JDK提供了volatile关键字...
  • java volatile 底层原理

    2018-02-28 14:45:31
    引言 在java语言中 被volatile 修饰的变量 可以保证在对线程场景下的可见性, 防止处理器进行指令重排 指令重排在多线程环境下会出现可见性问题 ...可见性原理 Volatile变量修饰符如果使...
  • volatile 底层原理解析

    2020-09-15 22:23:02
    并发和并行 并发:逻辑架构 交替跑,时间片上,会发生上下文切换 并行:物理架构 多cpu volatile 汇编翻译后会有一个lock指令(总线锁),多cpu加载同一个变量,不同的寄存器和高速缓存区会出现缓存不一致现象,...
  • volatile底层原理图解

    2020-08-21 11:22:48
    cpu层面上可见性和指令重排的由来 Java可见性和指令重排的由来
  • JMM&Volatile 底层原理

    2020-11-18 12:30:42
    占坑 笔记整理完更新
  • https://www.cnblogs.com/awkflf11/p/9218414.html
  • Singleton volatile instance = new Singleton(); // instance是volatile变量
  • 1.volatile的作用 1)保证线程间的可见性 2)防止指令重排 public class Test implements Runnable { boolean running = true; @Override public void run() { while(running){ } } public static void ...
  • volatile这个关键字可能...由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatile关键字的实现原理,最后给出了几个使用volatil
  • 学习volatile之前需要掌握 工作空间与主内存之间的交互 volatile的定义 Volatile关键字的语义分析 问题:10个线程分别对线程执行1000次i++操作,结果一定是10000吗? volatile的使用场景 volatile和...
  • 并发编程之JMM&Volatile底层原理剖析

    热门讨论 2020-10-15 14:13:59
    初步认识 Volatile 一段代码引发的思考,下面这段 代码演示了使用valatile和没有使用volatile关键字对于变量更新的影响 public class App { public volatile static boolean stop = false; public static void ...
  • 文章目录一、volatile的作用1.1、volatile变量的可见性1.2、volatile变量的禁止指令重排序二、volatile的的底层实现2.1、 Java代码层面2.2、字节码层面2.3、JVM源码层面2.4、汇编层面2.5、硬件层面 volatile关键字是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 38,669
精华内容 15,467
关键字:

volatile底层原理