zoukankan      html  css  js  c++  java
  • 深入分析synchronized的实现原理

    基础概念

      synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时可以保证共享变量对内存可见性。

      Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

      1. 普通同步方法,锁是当前实例对象
      2. 静态同步方法,锁是当前类的class对象
      3. 同步方法块,锁是括号里面的对象

      当一个线程访问同步代码块时,它首先是需要得到锁才能执行同步代码,当退出或者抛出异常时必须要释放锁。

    底层实现

    如何来实现这个机制呢?我们先看如下一段简单代码:

    public class SynchronizedTest{
        public synchronized void test1(){
    
        }
        public void test2(){
            synchronized(this){
            }
        }
    
        public static void main(String []args){
            
        }
    }

    利用javap工具查看生成的class 文件信息来分析synchronize的实现

      从上图可以看出,同步代码块是使用monitorenter和monitorexit指令实现的,同步方法(在这看不出来需要看JVM底层实现)依靠的是方法修饰符上的ACC_SYNCHRONIZED实现。

      在java语言中存在两种内建的synchronized语法:1、synchronized语句;2、synchronized方法。对于synchronized语句当Java源代码被javac编译成bytecode的时候,会在同步块的入口位置和退出位置分别插入monitorenter和monitorexit字节码指令。而synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。

    Java synchronized 包含两方面的含义

    互斥

    JVM 通过对象锁来实现互斥:

    typedef struct object {  
        uintptr_t lock;  
        Class *class;  
    } Object 

    协作
    协作是通过 Object wait, notify/notifyAll 方法实现的。

    对应到JVM 的底层术语,这一机制叫做 monitor:

    typedef struct monitor {
        pthread_mutex_t lock;
        Thread *owner;
        Object *obj;
        int count;
        int in_wait;
        uintptr_t entering;
        int wait_count;
        Thread *wait_set;
        struct monitor *next;
    } Monitor;

    在底层,进行获取 lock 动作到获得 lock 之间有一小段状态叫做 BLOCKED:  

    void monitorLock(Monitor *mon, Thread *self) {
        if(mon->owner == self)
                mon->count++;
            else {
                    if(pthread_mutex_trylock(&mon->lock)) {
                    disableSuspend(self);
    
                self->blocked_mon = mon;
                self->blocked_count++;
                self->state = BLOCKED;
    
                pthread_mutex_lock(&mon->lock);
            
                self->state = RUNNING;
                self->blocked_mon = NULL;
    
                        enableSuspend(self);
                    }
                    mon->owner = self;
            }
    }  

    下面我们来了解两个重要的概念:Java对象头,Monitor。

    对象头(Object Header):

     

      在JVM中创建对象时会在对象前面加上两个字大小的对象头,在32位机器上一个字为32bit,根据不同的状态位Mark World中存放不同的内容,如上图所示在轻量级锁中,Mark Word被分成两部分,刚开始时LockWord为被设置为HashCode、最低三位表示LockWord所处的状态,初始状态为001表示无锁状态。Klass ptr指向Class字节码在虚拟机内部的对象表示的地址。Fields表示连续的对象实例字段。

     

    Monitor Record:

       Monitor Record是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表;那么这些monitor record有什么用呢?每一个被锁住的对象都会和一个monitor record关联(对象头中的LockWord指向monitor record的起始地址,由于这个地址是8byte对齐的所以LockWord的最低三位可以用来作为状态位),同时monitor record中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。如下图所示为Monitor Record的内部结构:

     

    Owner:初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL;

    EntryQ:关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record失败的线程。

    RcThis:表示blocked或waiting在该monitor record上的所有线程的个数。

    Nest:用来实现重入锁的计数。

    HashCode:保存从对象头拷贝过来的HashCode值(可能还包含GC age)。

    Candidate:用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降。Candidate只有两种可能的值0表示没有需要唤醒的线程1表示要唤醒一个继任线程来竞争锁。

  • 相关阅读:
    mysql
    selenium
    解决servlet响应乱码问题
    flask后端的跨域问题
    python中并发----线程的启动和停止
    react-native 自定义组件规范
    react-native 高阶组件笔记
    class-dump安装及使用
    jekyll的安装
    取巧的json转model声明代码的工具
  • 原文地址:https://www.cnblogs.com/ktao/p/8454274.html
Copyright © 2011-2022 走看看