美文网首页一些收藏
Java多线程-深入

Java多线程-深入

作者: Zeppelin421 | 来源:发表于2022-04-14 19:25 被阅读0次

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);
}
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

// jdk8u - unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
    UnsafeWrapper("Unsafe_ComapreAndSwapInt");
    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
// 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) "cmpxchg1 %1.(%3)"
                        : "=a" (exchange_value)
                        : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                        : "cc", "memory");
    return exchange_value
}

最终实现:
cmpxchg = cas修改变量值

lock cmpxchg 指令

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

对象在内存中的存储布局

对象在内存中的存储布局
JVM常用参数
  • jvm参数
    64位系统指针默认是64bit
    工具:JOL = Java Object Layout
Object o = new Object();
System.out.println(ClassLayout.parseIntance(o).toPrintable());

markword
对象头 8字节。标识着头对象的状态,被回收多少次
class pointer
-XX:+UseCompressedClassPointers 为4字节,不开启为8字节
T t = new T() t里面的class pointer指向t.class

.class文件被ClassLoader加载到内存空间的时候会为每个.class生成一个对象
通常来说一个.class文件只会被一个ClassLoader加载

instance data
成员变量
-XX:+UseCompressedOops 为4字节,不开启为8字节
Ordinary Object Pointer 引用类型
padding
8的倍数。数据是一块一块读取的,所以需要对齐。

锁升级过程

锁升级

https://www.jianshu.com/p/9998a9db17f7

锁状态

无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
锁只能升级不能降级,jdk1.6之前都是重量级锁,为了降低获取锁的代价,才引入锁升级

偏向锁

大多数情况下,锁不存在多线程竞争,而是总由同一线程多次获取,为了使线程获得锁的代价更低而引入了偏向锁

偏向锁的获取

  • 首先获取锁对象的markword,判断是否处于可偏向状态(biased_lock=1 & ThreadId = null)
  • 当前可偏向状态,则通过CAS操作将当前线程ID写入到markword中
  • 如果是已偏向状态,需要检测markword中存储的ThreadId是否为当前线程的,如果是不需要再次获得锁,如果不是说明当前锁存在竞争,需要升级到轻量级锁

偏向锁的撤销

线程是不会主动释放偏向锁的,只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会撤销偏向锁

  • 偏向锁的撤销需要等待全局安全点,即在某个时间点上没有字节码正在执行是,它会先暂停拥有偏向锁的线程,然后判断锁对象是否处于被锁定状态。如果线程不处于活动状态,则将对象头设置为无锁状态,并撤销偏向锁(标志位0)或者轻量级锁(标志位00)的状态
轻量级锁

锁获取

  • 线程在自己的栈帧中创建锁记录LockRecord,用于存储锁对象markword的拷贝
  • 使用CAS操作尝试将锁对象的markword指向LockRecord,并将LockRecord的owner指向markword

未获取到锁的线程将通过自旋方式继续等待,如果自旋次数超过10次(-XX:PreBlockSpin控制) 或者自旋线程数超过CPU核数的一半(1.6后增加AdapativeSelfSpinning JVM自己控制)则轻量级锁升级为重量级锁

重量级锁

重量级锁通过对象内部的监视器monitor实现,monitor的本质是依赖于底层操作系统的mutex lock实现。操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本高。

锁粗化

加锁解锁需要消耗资源,如果存在一系列的连续加锁解锁操作,会导致不必要的性能损耗。
锁粗化就是将多个连续的加锁、解锁操作链接在一起,扩展成一个范围更大的锁,避免频繁的加锁解锁操作

for (int i = 0; i < size; i++) {
    synchronized(lock) {}
}
// 锁粗化
synchronized(lock) {
  for (int i = 0; i < size; i++) {}
}
锁消除

Java虚拟机在JIT编译时,通过对运行上下文的扫描,经过逃逸分析,去除不可能在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间

public synchronized void append(List<String> args) {
    StringBuffer temp = new StringBuffer();
    args.stream().forEarch(p -> temp.append(p));
    return temp.toString();
}

ObjectMonitor

class ObjectMonitor {
    protected:
        ObjectWaiter * volatile _WaitSet; // LL of threads waiting on the monitor
        ObjectWaiter * volatile _EntryList; // Threads blocked on entry or reentry
        void * volatile _owner; // pointer to owning thread OR BaiscLock
        volatile jlong _previous_owner_tid; // thread id of the previous owner of the monitor
        ObjectWaiter * volatile _cxq; // LL of recently-arrived threads blocked on entry
                                     // The list is actually composed of WaitNodes, acting as proxies for Threads
    private:
        volatile int _WaitSetLock; // protects Wait Queue - simple spinlock

    ObjectMonitor() {
        _header             = NULL;
        _count              = 0;
        _waiters            = 0;
        _recursions         = 0;
        _object             = NULL;
        _owner              = NULL;
        _WaitSet            = NULL;
        _WaitSetLock        = 0;
        _Responsible        = NULL;
        _succ               = NULL;
        _cxq                = NULL;
        FreeNext            = NULL;
        _EntryList          = NULL;
        _SpinFreq           = 0;
        _SpinClock          = 0;
        OwnerIsThread       = 0;
        _previous_owner_tid = 0;
    }
}

synchronized修饰的方法(代码块):

  • 当多个线程同时访问该方法,那么这些线程会先被放进_EntryList队列,此时线程处于Blocked状态
  • 当一个线程获取到了实例对象的监控器(monitor)锁,那么就可以进入Running状态执行方法,此时ObjectMonitor对象的_owner指向当前线程,_count加1表示当前对象锁被一个线程获取
  • 当Running状态的线程调用wait()方法,那么当前线程释放monitor对象进入Waiting状态,ObjectMonitor对象的_owner变为null,_count减1,同时线程进入_WaitSet队列,直到有线程调用notify()方法唤醒该线程,则该线程重新获取monitor对象进入_owner区
  • 如果当前线程执行完毕,那么也释放monitor对象,进入Waiting状态,ObjectMonitor对象的_owner变为null,_count减1

synchronized实现

java源码层级:synchronized(o) / synchronized(.class)
字节码层级:monitorenter&monitorexit / ACC_SYNCHRONIZED
JVM层级:执行过程中自动锁升级
CPU汇编层级:lock cmpxchg

synchronized vs Lock(CAS)

在高争用 高耗时的环境下synchronized效率更高
在低争用 低耗时的环境下CAS效率更高
synchronized到重量级之后是等待队列,不消耗CPU
CAS等待期间消耗CPU

JMM(Java Memory Model)

原子性
指一个操作是不可中断的,即使是在多个线程一起执行的时候。一个操作一旦开始,就不会被其他线程干扰。例如CPU中的一些指令属于原子性的。i=1属于原子性,i++不属于原子性

有序性
指在单线程环境中,程序是按序依次执行的。

可见性
指当一个线程修改了某个共享变量的值,其他线程是否能够立即知道这个修改。

指令重排

指令重排是指在程序执行过程中,为了性能考虑,编译器和CPU可能会对指令重新排序,以达到最佳效果

编译器重排

// 优化前
int x = 1;
int y = 2;
int a1 = x * 1;
int b1 = y * 1;
int a2 = x * 2;
int b2 = y * 2;

// 优化后
int x = 1;
int y = 2;
int a1 = x * 1;
int a2 = x * 2;
int b1 = y * 1;
int b2 = y * 2;

CPU只读一次的x和y值,不需反复读取寄存器来交替x和y值

处理器重排
一条汇编指令的执行分为很多步骤:

  • 取指IF
  • 译码和取寄存器操作数ID
  • 执行或者有效地址计算EX(ALU逻辑计算单元)
  • 存储器访问MEM
  • 写回WB(寄存器)
// 初始化
int a = 0;
int b = 0;
int x = 0;
int y = 0;

// 处理器A执行
a = 1; // A1
x = b; // A2
// 处理器B执行
b = 2; // B1
y = a; // B2
image.png

写缓存区没有及时刷新,使得处理器执行的读写操作与内存上顺序不一致。

相关文章

网友评论

    本文标题:Java多线程-深入

    本文链接:https://www.haomeiwen.com/subject/satfdltx.html