JVM 中synchronized的底层实现原理解析

锁是应用开发中的一种常见同步机制, 而synchronized则是java中的一种内建的同步方式, 所以也有人称其为Intrinsic Locking, 它提供了互斥的语义和可见性保证, 当一个线程已经获取当前对象锁时, 其他试图获取同一个锁的线程只能等待或者阻塞

一. 首先我们需要理解什么是线程安全

在拥有共享数据的多个线程并行执行的程序中,线程安全需要通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。即需要保证多线程环境下共享的, 可修改的状态[数据]的正确性;
反向来讲, 如果状态不是共享, 或者不可修改的, 也就不存在线程安全问题, 进而可以推理出保证线程安全的两个办法:
1) 封装: 通过封装, 我们将对象内部状态隐藏, 保护起来, 对外提供统一的多线程修改正确性表现;
2) 不可变: 设置状态初始化后就不能再改变, 比如声明final和immutable;

二. 线程安全需要保证的几个特性

  1. 原子性: 简单来说就是一系列操作在执行过程中不会被其他线程干扰, 对资源的访问一般表现为独占, 这一点一般通过同步机制实现;
  2. 可见性: 指一个线程修改了某个共享变量, 其状态能够立即被其他线程知晓, java共享内存模型下, 通常就是指线程本地状态立刻反映到主内存上, 这样就能被其他线程感知, volatile就有保证可见性的功能;
  3. 有序性: 保证线程内串行执行的语义, 避免指令重排序, 因为指令重排可能会导致最终结果非预料的;

在Java 5以前, synchronized是仅有的同步手段, 在代码中synchronized可以用来修饰方法, 代码块, 本质上都是将锁加到某个对象上面;

三. 用代码来看synchronized的功能作用

package org.hinsteny.jvm.synchronize;

/**
 * @author Hinsteny
 * @version $ID: ThreadSafeSample 2018-06-30 10:57 All rights reserved.$
 */
public class ThreadSafeSample {

    private int sharedState;

    public static void main(String[] args) throws InterruptedException {
        testNonSafeAction();
        testSafeAction();
    }

    private static void testNonSafeAction() throws InterruptedException {
        ThreadSafeSample sample = new ThreadSafeSample();
        Thread a = new Thread(() -> {
           sample.nonSafeAction();
        });
        Thread b = new Thread(() -> {
            sample.nonSafeAction();
        });

        a.start();
        b.start();
        a.join();
        b.join();
    }

    private static void testSafeAction() throws InterruptedException {
        ThreadSafeSample sample = new ThreadSafeSample();
        Thread a = new Thread(() -> {
           sample.safeAction();
        });
        Thread b = new Thread(() -> {
            sample.safeAction();
        });

        a.start();
        b.start();
        a.join();
        b.join();
    }

    private void nonSafeAction() {
        while (sharedState < 100000) {
            int former = sharedState++;
            int latter = sharedState;
            if (former != latter - 1) {
                System.err.println(String.format("Observed data state, former is %s, latter is %s", former, latter));
                break;
            }
        }
        System.err.println(String.format("Observed data state thread [%s] is finished", Thread.currentThread().getName()));
    }

    private void safeAction() {
        while (sharedState < 100000) {
            synchronized (this) {
                int former = sharedState++;
                int latter = sharedState;
                if (former != latter - 1) {
                    System.err.println(String.format("Observed data state, former is %s, latter is %s", former, latter));
                }
            }
        }
        System.err.println(String.format("Observed data state thread [%s] is finished", Thread.currentThread().getName()));
    }

}

运行结果为:

Observed data state, former is 6804, latter is 6916
Observed data state thread [Thread-1] is finished
Observed data state thread [Thread-0] is finished
Observed data state thread [Thread-2] is finished
Observed data state thread [Thread-3] is finished

反编译synchronized语句块得到:

private void safeAction();
        Flags: PRIVATE
        Code:
                  linenumber      59
               0: aload_0         /* this */
               1: getfield        org/hinsteny/jvm/synchronize/ThreadSafeSample.sharedState:I
               4: ldc             100000
               6: if_icmpge       81
                  linenumber      60
               9: aload_0         /* this */
              10: dup            
              11: astore_1       
              12: monitorenter   
                  linenumber      61
              13: aload_0         /* this */
              14: dup            
              15: getfield        org/hinsteny/jvm/synchronize/ThreadSafeSample.sharedState:I
              18: dup_x1         
              19: iconst_1       
              20: iadd           
              21: putfield        org/hinsteny/jvm/synchronize/ThreadSafeSample.sharedState:I
              24: istore_2        /* former */
                  linenumber      62
              25: aload_0         /* this */
              26: getfield        org/hinsteny/jvm/synchronize/ThreadSafeSample.sharedState:I
              29: istore_3       
                  linenumber      63
              30: iload_2         /* former */
              31: iload_3        
              32: iconst_1       
              33: isub           
              34: if_icmpeq       66
                  linenumber      64
              37: getstatic       java/lang/System.err:Ljava/io/PrintStream;
              40: ldc             "Observed data state, former is %s, latter is %s"
              42: iconst_2       
              43: anewarray       Ljava/lang/Object;
              46: dup            
              47: iconst_0       
              48: iload_2         /* former */
              49: invokestatic    java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
              52: aastore        
              53: dup            
              54: iconst_1       
              55: iload_3        
              56: invokestatic    java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
              59: aastore        
              60: invokestatic    java/lang/String.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
              63: invokevirtual   java/io/PrintStream.println:(Ljava/lang/String;)V
                  linenumber      66
              66: aload_1        
              67: monitorexit    
              68: goto            78
              71: astore          4
              73: aload_1        
              74: monitorexit    
              75: aload           4
              77: athrow         
              78: goto            0
                  linenumber      68
              81: getstatic       java/lang/System.err:Ljava/io/PrintStream;
              84: ldc             "Observed data state thread [%s] is finished"
              86: iconst_1       
              87: anewarray       Ljava/lang/Object;
              90: dup            
              91: iconst_0       
              92: invokestatic    java/lang/Thread.currentThread:()Ljava/lang/Thread;
              95: invokevirtual   java/lang/Thread.getName:()Ljava/lang/String;
              98: aastore        
              99: invokestatic    java/lang/String.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
             102: invokevirtual   java/io/PrintStream.println:(Ljava/lang/String;)V
                  linenumber      69
             105: return         
        Exceptions:
            Try           Handler
            Start  End    Start  End    Type
            -----  -----  -----  -----  ----
            13     68     71     78     Any
            71     75     71     78     Any

可以看到synchronized底层使用monitorenter/monitorexit实现了同步的语义

结论: 多线程访问共享变量时, 很容易发生互相干扰, 产生预期之外的结果;

四. Monitor 对象是同步的基本实现单元

在java 6 之前, Monitor 的实现完全是依靠操作系统内部的互斥锁, 因此需要进行用户态到内核态的切换, 所以同步操作是一个无差别的重量级操作;
现代的(Oracle) JDK中, JVM对此进行了大刀阔斧地改进, 提供了三种不同的Monitor实现, 也就是常说的三种不同的锁: 偏向锁(Biased Locking), 轻量级锁和重量级锁, 大大的改进了其性能.
而所谓锁的升级, 降级, 就是JVM优化synchronized运行的机制, 当JVM检测到不同的竞争情况时, 会自动切换到适合的锁实现, 这种切换就是锁的升级, 降级.

  1. 当没有竞争出现时, 会默认使用偏向锁, JVM 会利用CAS操作, 在对象头上的Mark Word部分设置线程ID, 以表示这个对象偏向于当前线程, 所以不涉及真正的互斥锁. 这样的假设是基于在很多应用场景中, 大部分对象生命周期中最多会被一个线程锁定, 使用偏向锁可以减低无竞争开销;
  2. 如果有其他线程试图锁定某个已经被偏向锁过的对象, JVM 就需要撤销(revoke) 偏向锁, 并切换到轻量级锁实现.
  3. 轻量级锁依赖CAS操作Mark Word来试图获取锁, 如果重试成功, 就是用普通的轻量级锁; 否则, 进一步升级为重量级锁.
  4. 当JVM进入安全点的时候, 会检查是否有闲置的Monitor, 然后试图进行锁降级;

五. 从源码层面, 看看synchronized的底层实现

查看最新的JVM源码地址: hotspot
从java对象头描述文件中看对象头结构 markOop

64位对象头结构

1 . sharedRuntime.cpp/hpp: 解释器和编译器运行时的基类. 下面的代码提现了synchronized的主要执行逻辑;

// Handles the uncommon case in locking, i.e., contention or an inflated lock.
JRT_BLOCK_ENTRY(void, SharedRuntime::complete_monitor_locking_C(oopDesc* _obj, BasicLock* lock, JavaThread* thread))
  if (!SafepointSynchronize::is_synchronizing()) {
    // Only try quick_enter() if we're not trying to reach a safepoint
    // so that the calling thread reaches the safepoint more quickly.
    if (ObjectSynchronizer::quick_enter(_obj, thread, lock)) return;
  }
  // NO_ASYNC required because an async exception on the state transition destructor
  // would leave you with the lock held and it would never be released.
  // The normal monitorenter NullPointerException is thrown without acquiring a lock
  // and the model is that an exception implies the method failed.
  JRT_BLOCK_NO_ASYNC
  oop obj(_obj);
  if (PrintBiasedLockingStatistics) {
    Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
  }
  Handle h_obj(THREAD, obj);
  if (UseBiasedLocking) {
    // Retry fast entry if bias is revoked to avoid unnecessary inflation
    ObjectSynchronizer::fast_enter(h_obj, lock, true, CHECK);
  } else {
    ObjectSynchronizer::slow_enter(h_obj, lock, CHECK);
  }
  assert(!HAS_PENDING_EXCEPTION, "Should have no exception here");
  JRT_BLOCK_END
JRT_END

2 . UseBiasedLocking: 一个基础设置, 在JVM启动时可以人为指定是否开启偏向锁; 其实偏向锁并不适合所有应用场景, 因为撤销操作(revoke)是比较重的行为, 只有当存在较多不会真正竞争的synchronized块时, 才能体现出明显改善; 因此实践中, 还是需要考虑具体业务场景, 并测试后, 再决定是否开启/关闭偏向锁, 关闭偏向锁的指令;

-XX:-UseBiasedLocking

3 . fast_enter: 就是完整的锁获取逻辑, 下面看一下 synchronizer 中的获取锁逻辑

void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock,
                                    bool attempt_rebias, TRAPS) {
  // 1.先判断应用程序是否启动了偏向锁
  if (UseBiasedLocking) {
    // 2.检测当前是否处于安全点
    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);
}

4 . 不能获取到偏向锁, 那再看看slow_enter的逻辑;

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.
    // 将目前的mark_word复制到 Displaced Header上
    lock->set_displaced_header(mark);
    // 利用CAS设置对象的Mark Word
    if (mark == obj()->cas_set_mark((markOop) lock, 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;
  }

  // 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.
  // 重置 Displaced Header
  lock->set_displaced_header(markOopDesc::unused_mark());
  ObjectSynchronizer::inflate(THREAD,
                              obj(),
                              inflate_cause_monitor_enter)->enter(THREAD);
}
  • 设置 Displaced Header,然后利用 cas_set_mark 设置对象 Mark Word,如果成功就成功获取轻量级锁。
  • 否则 Displaced Header,然后进入锁膨胀阶段,具体实现在 inflate 方法中。

六. 关于synchronized的一些使用建议

虽然synchronized在使用层面简单易用, 同时新一代的JVM也对它的底层实现做了一些优化, 但是在实际业务场景中, 我们还是要看具体看锁的粒度, 竞争情况, 锁类型(读/写)等多个条件, 再决定使用哪种锁;
常见的锁:

synchronized: 在锁竞争情况比较少发生的情况下, 重发发挥偏向锁的优势, 效率则比较高;
ReentrantLock:
ReentrantReadWriteLock:
StampedLock:

猜你喜欢

转载自blog.csdn.net/hinstenyhisoka/article/details/80864378