关于java:纯干货从源码解析多线程与高并发再说不会我不再踏足IT圈

9次阅读

共计 10479 个字符,预计需要花费 27 分钟才能阅读完成。

没什么太多说的,多线程与高并发,面试重点,咱间接进入正题,联结底层源码,咱们从源码看一下,多线程与高并发底层的知识点,这也是阿里 p8+ 的面试官倡议的学习到的级别

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 指令在执行前面指令的时候锁定一个北桥信号

(不采纳锁总线的形式)

工具: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 markword 实现表:

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

synchronized 优化的过程和 markword 非亲非故

用 markword 中最低的三位代表锁状态 其中 1 位是偏差锁位 两位是一般锁位

  1. Object o = new Object() 锁 = 0 01 无锁态
  2. o.hashCode() 001 + hashcode00000001 10101101 00110100 00110110
    01011001 00000000 00000000 00000000little 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 空幻体外),使得这一连串操作只须要加一次锁即可。

关注公众号:Java 架构师联盟,每日更新技术好文

局部材料曾经上传到我的 git 仓库中:有须要的能够下载

https://gitee.com/biwangsheng/mxq

正文完
 0