synchronized的一点学习

1,简介

关于java的同步,阻塞,唤醒等操作的具体实现原理,最近很想了解一下,但是没有时间去具体去看虚拟机的代码,所以想看看有没有别人已经造好的轮子。感谢分享的互联网的世界。

同步实现包含的东西

我们每次需要同步的时候都是直接使用了synchronized,但是仔细考虑的话,这个里面实际上包含了两层意思。

一方面是包括了互斥,也就是说对于共享资源的操作只能有一个线程能够访问临界区,

另一方面,在一个线程进入了临界区,其他线程一定是处于等待或者阻塞的状态的。

所以在synchronized处肯定是完成了互斥 和 等待/阻塞 的

2,一个synchronized的代码

源码


public class Syntest {

   public static void testSyn(String param) {
       synchronized (Syntest.class) {
           System.out.println(param);

       }
   }

}

反编译以后的代码


chuangchuang@chuang:~/work/git_lab/cajl-approve/target/test-classes/chen/thread[master*]$ javap -c   syn/Syntest.class 

Compiled from "Syntest.java"

public class chen.thread.syn.Syntest {

  public chen.thread.syn.Syntest();

    Code:

       0: aload_0

       1: invokespecial #1                  // Method java/lang/Object."<init>":()V

       4: return



  public static void testSyn(java.lang.String);

    Code:

       0: ldc           #2                  // class chen/thread/syn/Syntest

       2: dup

       3: astore_1

       4: monitorenter

       5: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;

       8: aload_0

       9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

      12: aload_1

      13: monitorexit

      14: goto          22

      17: astore_2

      18: aload_1

      19: monitorexit

      20: aload_2

      21: athrow

      22: return

    Exception table:

       from    to  target type

           5    14    17   any

          17    20    17   any

}

可以看到在编译后的代码中加入了两个指令

在code区的第 4 节有一个 monitorenter 指令

第19节有一个 monitorexit 指令

jvm也就是在monitorenetr指令的执行过程中完成了互斥和等待/阻塞

3, synchronized具体实现

synchronized 在jdk1.5之前听说是性能不太好,主要是只要使用synchronize就会产生操作系统的monitor的获取,但是这个同步块可能只有一个线程会访问,或者根本不会出现竞争的场景(多个线程交替访问),在这样的场景下,使用synchronized频繁的加锁释放锁就有点不是很合适了(开销太大)。

从网上查了很多资料,对synchronized的实现,尤其是重量级锁的实现讲的都是含糊其词,都说是交给操作系统,并没有show the code 到底是怎么和操作系统关联的。看得实在难受,大概翻了几百篇博客,受不了了,尝试看了一下jvm的实现,下面介绍一下自认为看到的东西,希望有人站出来打脸,帮忙斧正。

synchronized 的实现现在优化了很多,增加了偏向锁,轻量级锁,在进入synchronized的代码的时候,有可能会经历从 偏向锁-->轻量级锁--->重量级锁 的升级过程。但是也有可能不会升级,看对临界区的竞争情况。

3.1 monitorenter 对应的jvm代码入口

monitorenter时会进入到InterpreterRuntime.cpp的InterpreterRuntime::monitorenter函数


//%note monitor_1    

//参数 JavaThread* thread 指向的是当前线程

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");    

// UseBiasedLocking 标识了虚拟机是否可以使用偏向锁

 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    

方法入参解析

1、JavaThread thread指向java中的当前线程;
2、BasicObjectLock类型的elem对象包含一个BasicLock类型_lock对象和一个指向Object对象(synchronized包裹的对象)的指针_obj;


class BasicObjectLock {

  BasicLock _lock;  // object holds the lock; 

  oop  _obj;  

 }

3、BasicLock类型_lock对象主要用来保存_obj指向Object对象的对象头数据;


class BasicLock {   

 volatile markOop _displaced_header; 

}

4、UseBiasedLocking标识虚拟机是否开启偏向锁功能,如果开启则执行fast_enter逻辑,否则执行slow_enter;

3.2 进入偏向锁

如果开启了偏向锁,则优先通过


  ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);    

进入偏向锁


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) ;    

}    

通过调用BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);

来进行偏向锁的设置,老实说这个里面可能用的有些东西还没有弄太明白,偏向锁的获取逻辑有点复杂

只能看的大概的意思

1,获取对象头的Mark Word;
2,判断mark是否为偏向锁状态且mark word中的threadId 为null , 如果是,则进入3,否则进入6

3, 进行一次cas的偏向加锁操作,就是将 markword中存放的threadId替换为当前线程的id

4, cas成功则说明获取到偏向锁,返回,执行同步代码块。

5,cas失败,则等待安全点,释放偏向锁,通过轻量级锁进入。

6,如果mark标示为偏向锁状态,threadId为当前线程,则是重入锁,直接进入临界区。

3.3 偏向锁的撤销

偏向锁的存在的场景是在临界资源不会被两个活着的线程交替访问的状态,它包含了


在整个应用的周期只会有一个线程持有该偏向锁

在整个应用周期当下一个需要访问临界区的线程来临时,上一个线程已经挂掉了

所以偏向锁采取的是不主动撤销偏向锁的策略,线程会一直持有偏向锁,知道其他线程来访问临界资源。

这样的优势是不用撤销锁了,对比轻量级锁减小了退出的时候的cas操作

1,要到达一个全局安全点,safe point

2,暂停拥有偏向锁的线程,看是恢复到无锁还是轻量级锁状态

偏向锁可以通过启动参数关闭

XX:-UseBiasedLocking=false

4, 轻量级锁

偏向锁的存在的场景是在临界资源不会被两个活着的线程交替访问的状态

轻量级锁存在的场景是任意时刻不会有两个线程同时需要持有锁

对应的是多个线程交替的进入和退出临界区,没有竞争。

4.1 轻量级锁的进入

对应了


ObjectSynchronizer::slow_enter

在无法获取偏向锁的时候,会调用slow_enter

  1. 拷贝锁对象头mark到线程的 BasicLock 当中

  2. 通过cas尝试将 mark更新为指向 BasicLock的指针

  3. 2成功则执行同步代码块,失败的话进入4

  4. 2失败的话说明当前处于加锁状态,查看mark指向的 BasicLock 是否存储在当前线程的栈贞,如果是,则说明是重入,直接执行同步代码即可。否则,说明发生了竞争,则进行锁升级,轻量级锁膨胀为重量级锁。

4.2 轻量级锁的释放

入口是:ObjectSynchronizer::fast_exit

  1. 将保存在BasicLock中的数据通过cas换回到对象的mark当中

  2. 如果1 成功,则成功释放锁,否则,说明有其他线程在竞争锁,这个时候需要将该锁升级为重量级锁并后释放。

5, 重量级锁

这一次学习主要关注的也是重量级锁,因为看了很多文章说重量级锁是交给操作系统进行管理的,互斥等都是由操作系统来实现,对性能的影响很大,等等。但是我翻阅了很多文章都很难将这些东西说清楚,后面只能自己看代码来学习一下了。

在重量级锁中用到了一个叫 ObjectMonitor 的对象,这个对象被称为锁,将他绑定到synchronized使用的对象上(对象的mark指向 objectMonitor)

5.1 重量级锁的膨胀

使用的是


ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object)    

函数,这个里面的逻辑也是比较多,只能大体的介绍一下逻辑

重量级锁的膨胀的目的是为了拿到一个objectMonitor对象,并把这个objectMonitor绑定到synchronized使用的对象(obj)上

  1. jvm会维护一个全局的可用monitor, 叫gfree-monitors, 同时每个线程也会有自己私有的可用的monitor对象列表 omfreemMonitors

  2. 当前线程会先去omfreeMonitors当中寻找可用的objectMonitor对象,如果没有再去gfree-monitors中去取,如果gree-monitors当中也没有的话,就会new一个objectMonitor对象,然后使obj->mark指向该objectMonitor

3.这样的话,该膨胀就完成了,但是线程还需要对锁进行竞争。

5.2 对重量级锁的竞争

这个需要稍微了解一下objectMonitor的结构,这里做了一些删减,只留下了本次需要用到的字段


 ObjectMonitor() {    

     _owner        = NULL;    //存放该monitor被持有的线程的id

   _WaitSet      = NULL;    //调用obj.wait()方法的线程会被放入此线程

   _cxq          = NULL ;    // 竞争失败的线程会进入

   _EntryList    = NULL ;    

  }    

重量级锁涉及到了线程间的阻塞和唤醒,是通过locksupport中的park和unpark进行的支持,其中阻塞和等待锁的线程都在objectMonitor中记录。

先聊一下竞争的过程:

1.通过CAS尝试把monitor的_owner字段设置为当前线程,同时也会判断是否为重入锁等。

2.如果cas成功,或者为重入锁则返回进入到临界区。

3.如果竞争失败,则将当前线程放入_cxq队列,并且将当前线程park()。

5.3 重量级锁的释放

释放重量级锁的入口是ObjectMonitor::exit()

释放的逻辑是

1.把objectMonitor的owner字段设置为null

2.查看_EntryList是否为空,不为空则则从_EntryList中取出一个线程唤醒

3.如果_EntryList为空,则将 _cxq中的元素移动到_EntryList当中,然后再从_EntryList当中唤醒一个

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容