线程&Synchronized

概要 : 线程Thread 进程 纤程/协程Fiber

在这里插入图片描述

图 1 冯诺依曼计算机体系结构

进程与线程的区别

  • 进程:操作系统资源分配的基本单位
  • 线程:CPU执行的基本单位,也是一个进程里面最小的执行单元

一个CPU(核)在同一时刻可以执行几个线程?

​ 除非有超线程存在,否则1个

线程切换

ALU: 运算单元

Register: 寄存器

PC: 程序计数器

Cache: 缓存

线程切换的时候,需要把寄存器和程序计数器中的内容保存(保护线程),然后在切换到其他线程执行,再切换回来。

JVM 是运行在操作系统之上的一个软件,线程分为操作系统级别的线程以及JVM级别的纤程。JVM规范中并未规定JVM中的纤程和操作系统的线程如何对应,但是 HotSpot 中的实现是一一对应的。

synchronized 关键字在 jdk1.2 之前是重量级锁,所谓重量级,就是由操作系统来直接管理加锁以及释放锁的过程。后来经过一系列升级之后,synchronized 底层实现了 无锁 -> 偏向锁 -> 自旋锁 -> 重量级锁 的锁升级过程。

​ 如果不是操作系统直接管理,可以由虚拟机来管理,JVM 自己来管理 保护线程 的操作,多个纤程对应一个线程。在运算简单但是需要很多线程来运算的场景,纤程的实现比直接使用线程快很多。

Synchronized 如何实现

介绍 synchronized 之前,要先了解对象在内存中的存储形式

在这里插入图片描述

图 2 java 对象结构

对象的几个部分的作用:

  1. 对象头中的Mark Word(标记字)主要用来表示对象的线程锁状态,另外还可以用来配合 GC 以及存放该对象的 hashCode

  2. Klass Word 是一个指向方法区中 Class 信息的指针,意味着该对象可随时知道自己是哪个 Class 的实例;

  3. 数组长度也是占用64位(8字节)的空间,这是可选的,只有当本对象是一个数组对象时才会有这个部分;

  4. 对象体是用于保存对象属性和值的主体部分,占用内存空间取决于对象的属性数量和类型

  5. 对齐字是为了减少堆内存的碎片空间,提高内存读写效率。将整个对象占用的空间对齐为能被8字节整除

一、MarkWord标记字

在这里插入图片描述

  1. 使用2bit标志位来表示不同的状态,无锁 -> 偏向锁 -> 自旋锁 -> 重量级锁
  2. 分代年龄: 占用4bit,所以最大值是15,-XX:MaxTenuringThreshold 的最大值为15;
  3. identity_hashcode:31位的对象标识hashCode,采用延迟加载技术。调用方法System.identityHashCode() 计算,并会将结果写到该对象头中。当对象加锁后(偏向、轻量级、重量级),MarkWord 的字节没有足够的空间保存 hashCode,因此该值会移动到管程Monitor
  4. thread:持有偏向锁的线程ID。
  5. epoch:偏向锁的时间戳。
  6. ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针。
  7. ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针。
重量级锁的问题

​ 会造成线程排队(串行执行),且会使CPU用户态和核心态之间频繁切换,所以代价高、效率低。为了提高效率,不会一开始就使用重量级锁,JVM在内部会根据需要,按如下步骤进行锁的升级:

  1. 初期锁对象刚创建时,还没有任何线程来竞争,对象的Mark Word是上图中第一种情形,这偏向锁标识位是0,锁状态01,说明该对象处于无锁状态(无线程竞争它)。

  2. 当有一个线程来竞争锁时,先用偏向锁,表示锁对象偏爱这个线程。

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

这个线程要执行这个锁关联的任何代码,不需要再做任何检查和切换,这种竞争不激烈的情况下,效率非常高。这时Mark Word会记录自己偏爱的线程的ID(加锁的过程),把该线程当做自己的熟人。如上图中第二种情形。

  1. 当有两个线程开始竞争这个锁对象,情况发生变化了,不再是偏向(独占)锁了,锁会升级为轻量级锁,两个线程公平竞争,哪个线程先占有锁对象并执行代码,锁对象的 Mark Word 就执行哪个线程的栈帧中的锁记录。如上图第3.1种情形。

  2. 如果竞争的这个锁对象的线程更多,导致了更多的切换和等待,JVM 会把该锁对象的锁升级为重量级锁,这个就叫做同步锁,这个锁对象 Mark Word 再次发生变化,会指向一个监视器对象,这个监视器对象用集合的形式,来登记和管理排队的线程。如上图第3.2种情形。

  3. 锁标志位为 (1,1) 时,表示该对象被 GC 标记过了

    -XX:BiasedLockingStartupDelay=0
    

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

JOL工具

jol是一款方便地打印对象内存内容的工具

// https://mvnrepository.com/artifact/org.openjdk.jol/jol-core
compile group: 'org.openjdk.jol', name: 'jol-core', version: '0.10'
public class JolTest {
    private static AtomicInteger num = new AtomicInteger(0);
    private static ConcurrentHashMap<String, Long> map = new ConcurrentHashMap<>();

    public static void main(String[] args) {
//        Object o = new Object();
//        System.out.println(ClassLayout.parseInstance(o).toPrintable());

        num.incrementAndGet();
        map.put("abc", 100L);

        try {
            // sleep 4秒之后偏向锁启动
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        User u = new User();
        u.setId(12);
//        System.out.println(ClassLayout.parseInstance(u).toPrintable());
        u.setCode(Long.MAX_VALUE);
        u.setName("张三");
        System.out.println(ClassLayout.parseInstance(u).toPrintable());

        // 无锁状态
        synchronized (u) {
            System.out.println(ClassLayout.parseInstance(u).toPrintable());
        }

        System.gc();
        System.out.println(ClassLayout.parseInstance(u).toPrintable());

    }
}

synchronized 代码实现

  1. Java 层级
synchronized (o) 
  1. 字节码层级
monitorenter moniterexit
  1. JVM层级(Hotspot
package com.mashibing.insidesync;
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 方法:膨胀为重量级锁

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章