栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Synchronized

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Synchronized

  前言: 最近上操作系统课,老师在讲访问临界区(Critical Area)的方法,其实就多线程同时访问共享数据时保证不会出错的方法了,讲过采用信号量的方法之后,老师就讲了Monitor(管程)这个方法,就是采用Synchronized,我觉得还挺有意思的(况且从功利的角度看,面试容易出,嘿嘿嘿),遂记录与此,供以后复习用。

  1. Synchronized是什么?

   要学习一个东西,我们肯定先要知道它是什么了,在Java中,Synchronized其实就是一个关键字,用来实现线程依次排队操作共享变量的功能,从而实现线程的互斥,再翻译一下就是用了Synchronized之后就可以给代码块上锁,谁拿到这个锁谁才能访问那个代码(如果拿到锁的是类,那么该类的所有对象是同一把锁)。虽然听起来就效率很低的样子,但是咱毕竟还不会啥别的(好吧,不是咱,是我),所以好好开始学习吧。

   2.  Synchronized的使用

  上文我们也说了,synchronized就是java里面的一个关键字,所以肯定是用来修饰java的代码块里了。具体有以下几个位置可用:

  • public synchronized void method() { .......}

    1. 用来修饰方法,作用于实例方法,被锁为类的实例对象

  • public static synchronized void method1() { .......}

     2. 用来修饰静态方法,作用于类对象,被锁的为类对象 

  • synchronized (this) { .......}

     3. 用来修饰代码块,作用于实例对象,被锁的为类的实例对象 this表示当前实例

  • synchronized (SynchronizedScopeDemo.class) { .......}

      4. 被锁的为类对象

3. Synchronized的实现机制

     这里以重量级锁为例说明synchronized的实现机制,那为什么是重量级锁那,嘿嘿嘿,因为重量级锁为synchronized优化前的状态,而在JDK1.6之后对synchronized进行了优化,加入了偏向锁和轻量级锁。

    要想理解重量级锁,不得不提到对象头的概念了,什么是对象头那? 对象头是JVM中,对象在内存中存储的布局区域之一(另外还有两个区域,实例数据:存放类的属性数据信息和填充数据和填充数据),对象头又被分成两部分:Mark Word(标记字段) & Class Pointer(类型指针)。下面我们将目光转回我们的重量级锁,在重量级锁中,对象头由指向重量级锁(Monitor对象)的指针和锁标志位组成。

 

     由此可以观之: Monitor对象就是实现我们重量级锁的重点了,况且也是操作系统里我们学习的重点,已经迫不及待开始了那~

Monitor对象:

  Monitor对象被称之为管程,啦啦啦,就是操作系统里的那个管程呀!!!我觉得翻译的还不错,管程——顾名思义就是管理程序用的,它所做的也是管理我们的程序只能被得到锁的线程访问,而其他线程不能访问~~在Java之中,每个对象实例都会自动关联一个Monitor对象,这个Monitor对象与对象实例同生死,在线程视图获取对象锁时自动生成。当这个Monitor对象被线程持有时,它就被锁定了,其他线程就不能访问了。

让我们来看看Monitor在JVM的实现代码吧!

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; //记录线程获取锁的个数,获取到锁后count加1,释放锁后count减1    
    _waiters      = 0,
    _recursions   = 0;  // 线程重入次数
    _object       = NULL;
    _owner        = NULL;  //表示持有monitor对象的线程,初始值为NULL,表示没有线程持有,
    _WaitSet      = NULL; // 调用wait方法后的线程会被加入到_WaitSet队列中排着
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ; // 阻塞队列,线程被唤醒后(notify方法被调用)根据决策判读是放入cxq还是EntryList
    FreeNext      = NULL ;
    _EntryList    = NULL ; // 没有抢到锁的线程会被放到这个队列
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

  某个线程获取到对象的monitor后,便将monitor中的owner设置为该线程的ID,同时将计数器count++。调用锁对象的wait方法后,线程就会释放当前持有的monitor,并将owner重置为空,count--,然后把该线程放进 _WaitSet队列中等待被再次宠幸。

    然后那——这里面那三个队列存放的都是封装了线程的ObjectWaiter对象。略略略,具体的看代码上面的注释吧。大概就是,对已经得到锁的进程调用wait方法后,这个进程就去_WaitSet队列了,调用锁对象的notify方法后,根据情况的不同,判断这个进程是去_cxq还是去_EntryList。没抢到锁的进程就去_EntryList,获得锁的进程被释放之后,要么执行_EntryList中的进程,要么把_cxq中的进程转移到_EntryList然后再执行_EntryList中的进程。

 那么synchronized是如何通过Monitor对象实现线程间的互斥的那?

  synchronized在同步代码块的入口处加入 monitorenter,同步代码块出口处加入moniterexit。

  monitorenter执行时那,线程就会竭尽全力的获取该对象的锁,当该对象的moniter的count==0时,该线程就可成功获取这个锁了,并将count++。那如果偏偏是其他线程在拥有锁的话,那么对不起了,当前线程再努力也没用了,它只能乖乖去_EbtryList里和大家一起排队去了。直到等待的锁被释放(即执行到moniterexit指令结束),其他线程才能重新获得锁。

  同步方法与同步代码块的实现类似,就是会在其中加一个ACC_SYNCHRONIZED标记位用来标记该方法是否是可获取锁,其实就是用标记位代替了monitorenter和moniterexit的使用。

重量级锁会导致程序高频的发生用户态和内核态的转变,严重影响程序性能。当然,JDK1.6之后对重量级锁做了改进。我们下次再一起学习吧,今天就先学到这里吧~~~

  

 

写的非常非常好的参考文章:

这一次,彻底搞懂Java中的synchronized关键字 - 掘金 (juejin.cn)https://juejin.cn/post/6973571891915128846

  

  

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/931203.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号