一步一步学多线程-synchronized

author author     2022-09-15     428

关键词:

  当线程执行请求synchronized方法或块时,monitor会设置几个虚拟逻辑数据结构来管理这些多线程。

  技术分享

  

  请求的线程会首先被加入到线程排队队列中,线程阻塞,当某个拥有线程锁的线程unlock之后,则排队队列里的线程竞争上岗(synchronized是不公平竞争锁),如果运行的线程调用对象wait()后就释放锁并进入wait线程集合那边,当调用对象的notify()或notifyall()后,wait线程就到排队那边。

重量级锁

  在JVM规范中描述:每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

  1、  如果monitor的进入数为0,则该线程进入monitor,如果将进入数设置为1,该线程即为monitor的所有者。

  2、  如果线程已经占有monitor,只是重新进入,则进入monitor的进入数加1.

  3、  如果其他线程已经占用了monitor,则该线程进入阻塞状态,知道monitor的进入数为0,再重新尝试获取monitor的所有权。

   Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

  Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。

         但是其本质又是依赖于底层的操作系统的互斥锁(Mutex Lock)来实现的。而操作系统实现线程之间的切换这就需要用户转换到和心态,这个成本非常高,状态之间的转换需要相对较长的时间,这就是为什么Synchronized效率低的原因。

         因此,这种依赖于操作系统互斥锁(Mutex Lock)所实现的锁我们称之为“重量级锁”。

         当多线程环境进入synchronized区域的线程没有竞争时,JVM并不会马上创建重量级锁,而是使用偏向锁或者轻量级锁,当存在资源竞争的情况下才会使用重量级锁。

轻量级锁

  轻量级锁的核心思想:被加锁的代码不会发生并发,如果发生并发,那就膨胀成重量级锁(膨胀即是锁升级)。

       根据轻量级锁的实现,我们知道虽然轻量级锁不支持并发,遇到并发就要膨胀为重量级锁,但是轻量级锁可以支持多个线程以串行的方式访问同一个锁对象。

偏向锁

  偏向锁的核心思想:假设加锁的代码自始至终只有一个线程调用,如果发现多于一个线程调用,即使没有线程间竞争,也会把锁升级为轻量级锁。

自旋锁

  当线程阻塞后,如果进入排队队列需要CPU从用户态转为核心态,尤其当遇到频繁的阻塞和唤醒对CPU来说负荷很重。统计发现,很多对象锁的锁定状态持续的时间很短,此时在这么短的时间内进行线程频繁切换资源耗费严重。所以此时引出了自旋锁的概念。

   所谓“自旋”,就是monitor并不把线程阻塞放入排队队列,而是去执行一个无意义的循环,循环结束后看看是否锁已释放并直接进行竞争上岗步骤,如果竞争不到继续自旋循环,循环过程中线程的状态一直处于running状态。明显自旋锁似的synchronized的对象锁方式在线程之间引入了不公平。但是这样可以保证大吞吐率和执行效率。

   不过虽然自旋锁方式省去了阻塞线程的时间和空间(队列的维护等)开销,但是长时间自旋也是很低效的。所以自旋的次数一般控制在一个范围内,如10,50等(在JDK1.6中默认为10次),在超出这个范围后,线程就进入排队队列。

自适应自旋锁

  就是自旋的次数是通过JVM在运行时收集的统计信息,动态调整自旋锁的自旋次数上界。

对象头

         介绍了这几种锁,那么程序是通过什么来实现对象锁的呢?首先来看对象头的结构。

    技术分享

  在Hotspot虚拟机的对象头上主要包括两部分数据:Mark Word(标记字段),Klass Pointer(类型指针)。其中Klass Pointer是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,Mark Word用于存储对象自身的运行时数据,它是各种锁的关键。

  Mark Word中的结构大致如此

  技术分享

轻量级锁的获取和释放

获取锁

  1、  判断当前对象是否处于无锁状态,若是,则JVM首先将当前线程的栈帧中建立一个名为所记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝。否则执行步骤3。

  2、  JVM利用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,如果成功表示竞争到锁,则将锁标志位变成00(表示此对象处于轻量级锁状态),执行同步操作,如果失败则执行步骤3。

  3、  判断当前对象的Mark Word是否指向当前线程的栈帧,如果是则表示当前线程已经持有当前对象的锁,则直接执行同步代码块;否则只能说明该锁对象已经被其他线程抢占了,这时轻量级锁需要膨胀为重量级锁,锁标志位变成10,后面等待的线程将会进入阻塞状态。

释放锁

轻量级锁的释放也是通过CAS操作来进行的,主要步骤如下

1、  取出在获取轻量级锁保存在Mark Word中的数据;

2、  用CAS操作将取出的数据替换当前对象的Mark Word中,如果成功,则说明锁释放成功,否则执行3.

3、  如果CAS操作替换失败,说明有其他线程尝试获取该锁,则需要在释放所的同时需要唤醒被挂起的线程。

偏向锁的释放和获取

获取锁

  1、  检测Mark Word是否为可偏向状态,即是否为偏向锁1,锁表示为01;

  2、  若为可偏向状态,则测试线程ID是否为为当前线程ID,如果是,则执行步骤5,否则执行步骤3。

  3、  如果线程ID不是当前线程ID,则通过CAS操作竞争锁,竞争成功,则将Mark Word的线程ID替换为当前线程ID,否则执行步骤4。

  4、  通过CAS竞争锁失败,证明当前存在多线程竞争情况,当到达全局安全点,获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码块。

  5、  执行同步代码块。

释放锁

  偏向锁的释放采用了一种只有竞争才会释放锁的机制,线程会是不会主动释放偏向锁,需要等待其他线程来竞争。偏向锁的撤销需要等待全局安全点,步骤如下:

  1、  暂停拥有偏向锁的线程,判断锁对象是否还处于被锁定状态

  2、  撤销偏向锁,恢复到无锁状态或轻量级锁的状态。

参考资料

synchronized、锁、多线程同步的原理是咋样的

深入分析synchronized的实现原理

一步一步学rendermonkey

http://blog.csdn.net/tianhai110/article/details/5668832 转载请注明出处:http://blog.csdn.net/tianhai110/ 网上一些关于renderMonkey的教程:《RenderMonkey的基本使用方法》http://www.cnblogs.com/mixiyou/archive/2009/10/05/ 查看详情

一步一步学vue

接上篇,这次是真的接上篇,针对上篇未完成的部分,增加鉴权功能,开始之前,我们先要介绍一个新的知识,路由元数据。 在vue-router中,定义元数据的方式:constrouter=newVueRouter({routes:[{path:‘/foo‘,component:Foo,children:[{path:... 查看详情

一步一步学vue

为了提升代码的逼格,之后代码改为Vue文件组件,之前代码虽然读起来容易理解,而且适合在小的项目中使用,但是有如下缺点:全局定义(Globaldefinitions) 强制要求每个component中的命名不得重复字符串模板(Stringtemplates) 缺... 查看详情

一步一步学vue

  前言:我以后在文章最后再也不说我下篇博文要写什么,之前说的大家也可以忽略,如果你不忽略,会失望的??,不过说出去的话还是要表示一下的,简单介绍一下路由钩子:  正如其名,vue-router 提供的导航钩子主要... 查看详情

一步一步学vue

本篇完成如下场景:1、系统包含首页、客户信息查询、登录三个模块2、默认进入系统首页,如果要进行用户查询,则需要进行登录授权3、查询用户后点击列表项,则进入详情页面基于上述场景需求描述,在客户端我们考虑,需... 查看详情

一步一步学vue

...不同,我们会对其进行增删改查的基本操作,之后进行进一步的完善,按照常规的系统使用经验,一般我们新增和编辑都是在模态框中处理,这里我们不会去构建复杂的模态框,只用一个简单的div层来代替,后期接下来的文章中... 查看详情

一步一步学vue

本篇是是vue路由的开篇,会以一个简单的demo对vue-router进行一个介绍,主要覆盖以下几个常用场景:1、路由跳转2、嵌套路由3、路由参数 1、Vue-Router  一般来说,路由定义就是定义地址访问规则,然后由路由引擎根据这些... 查看详情

一步一步学jvm-垃圾回收算法

标记-清除算法        算法分为标记和清除两个阶段:首先标记所有需要回收的对象,在标记完成后统一回收所有被标记的对象。        该算法存在的缺点:  1、 ... 查看详情

一步一步学nlp:熟悉nlp

NLP学习AI工程师必备的核心技能现实生活中的问题---->数学优化问题---->通过合适的工具解决whatisNLPNLP=NLP+NLUNLU:语音/文本->意思(meaning)Natural+langugeUnderstandingNLG:意思->文本/语音Natural+LangugeGenerationwhatis... 查看详情

一步一步学nlp:熟悉nlp

NLP学习AI工程师必备的核心技能现实生活中的问题---->数学优化问题---->通过合适的工具解决whatisNLPNLP=NLP+NLUNLU:语音/文本->意思(meaning)Natural+langugeUnderstandingNLG:意思->文本/语音Natural+LangugeGenerationwhatis... 查看详情

一步一步学jvm-垃圾回收

  垃圾回收器在对对象进行回收前,首先要判断对象是否还“活着”。判断方法有以下两种引用计数法        给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1。当引用失... 查看详情

一步一步学zookeeper-zookeeper初了解

角色        Zookeeper中的角色主要有以下三类        领导者(Leader)             查看详情

一步一步学jvm-运行时数据区域

程序计数器(ProgramCounterRegister)        像我们平时读书一样,当我们在去做别的事情之前,我们会对我们读到什么地方了做一个标记,方便我们再回来的时候接着重新读。如果这本书有很多人读呢... 查看详情

一步一步学java:入门的基础知识

​​ JAVA入门的基础知识学的再多,也要记得复习复习基础知识丫;​ 基本类型及其转换数字中有byte,short,char,int,long,float,double的类型*在使用过程中:要注意在float后面加上F,在long后面加L;longi=10L;floatm=56.345F;char后... 查看详情

一步一步学jvm-java内存模型

主内存与工作内存        Java内存模型的主要目标是定义程序中各个变量的访问规则。即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。这里的变量和Java编程中所说的变量有所区... 查看详情

linux一步一步学linux——dnsdomainname命令(174)(代码片段)

00.目录文章目录00.目录01.命令概述02.命令格式03.常用选项04.参考示例05.附录01.命令概述dnsdomainname命令用于定义DNS系统中FQDN名称中的域名。dnsdomainname=hostname-d02.命令格式用法:dnsdomainname[-v]03.常用选项--help 显示帮助文档--ve... 查看详情

一步一步学ef系列6ioc之autofac

前言     之前的前5篇作为EF方面的基础篇,后面我们将使用MVC+EF并且使用IOC,Repository,UnitOfWork,DbContext来整体来学习。因为后面要用到IOC,所以本篇先单独先学习一下IOC,我们本本文单独主要学习Autofac,其实... 查看详情

一步一步学j2se-concurrenthashmap原理

  ConcurrentHshMap的数据结构是由一个Segment数组和多个HashEntry数组组成,在Segement数组中包含了HashEntry数组。数据结构如下图所示:  Segement数组的意义就是将一个大的table分割成多个小的table来加锁,而每一个Segment元素存储的... 查看详情