一步一步学多线程-synchronized

  当线程执行请求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的实现原理

时间: 2024-08-06 14:24:30

一步一步学多线程-synchronized的相关文章

Rhythmk 一步一步学 JAVA (21) JAVA 多线程

1.JAVA多线程简单示例 1.1 .Thread  集成接口 Runnable 1.2 .线程状态,可以通过  Thread.getState()获取线程状态: New (新创建) Runnable (可以运行) Blocked  (被阻塞) Waiting  (等待) Timed waiting (计时等待) Terminated  (被终止) ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

Rhythmk 一步一步学 JAVA (20) JAVA enum常用方法

JAVA 枚举定义常用方法: 1.static Enum valueOf(Class enum,String name) 返回指定name的枚举类型 2.Static Enum values[] 返回枚举常量集合 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50

一步一步学solr:在开始前我们应该明白什么

我就用自己的项目来讲solr应用了,当然他的功能很多,大家可以看这里 http://my.oschina.net/fengnote/blog/288581 功能那是相当的多. solr可以理解为与应用分离的一个搜索服务,我们要搭建应用+搜索服务的关联配置实现部分业务. 我们的项目现在要改功能,一个内容发布系统,做一个站内搜索,原有的框架是SSI的,只把查询部分用solr来实现. 问题是: 我要查询一篇文章关联到N张表 我除了查询文章还要查询分类(也用solr实现) 新增.修改.删除文章/分类后要

一步一步学WebSocket(二) 使用SuperWebSocket实现自己的服务端

上一篇文章,我们了解了客户端如何与服务器创建WebSocket连接.但是一个巴掌拍不响,既然是通信,就必然最少要有两个端.今天我们来看看c#如何用已有的框架实现一个WebSocket服务端. 在.Net Framework 4.5及以上版本中,微软为我们集成了WebSocket协议的基本实现.微软提供的WebSocket对象位于System.Net.WebSocket命名空间下,使用起来挺繁琐的,所以我选择了SuperWebSocket框架来简化开发的难度. SuperWebSocket框架可以

Rhythmk 一步一步学 JAVA (22) JAVA 网络编程

1.获取主机信息 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test     public void GetDomainInfo() throws UnknownHostException {         String domain = "www.baidu.com";         InetAddress netAddress = InetAddress.getByName(domain);         // 获取

一步一步学ROP之linux_x64篇

一步一步学ROP之linux_x64篇 一.序 **ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防御(比如内存不可执行和代码签名等).上次我们主要讨论了linux_x86的ROP攻击:<一步一步学ROP之linux_x86篇>,在这次的教程中我们会带来上一篇的补充以及linux_x64方面的ROP利用方法,欢迎大家继续学习. 另外文中涉及代码可在我的github下载:https://githu

一步一步学ios UITextView(多行文本框)控件的用法详解(五5.8)

本文转载至 http://wuchaorang.2008.blog.163.com/blog/static/48891852201232014813990/ 1.创建并初始化 创建UITextView的文件,并在.h文件中写入如下代码: [csharp] view plaincopy #import <UIKit/UIKit.h> @interface TextViewController : UIViewController <UITextViewDelegate> { UITe

【DG】[三思笔记]一步一步学DataGuard

[DG][三思笔记]一步一步学DataGuard 它有无数个名字,有人叫它dg,有人叫它数据卫士,有人叫它data guard,在oracle的各项特性中它有着举足轻理的地位,它就是(掌声)......................Oracle Data Guard.而对于我而言,我一定要亲切的叫它:DG(注:主要是因为打着方便). 不少未实际接触过dg的初学者可能会下意识以为dg是一个备份恢复的工具.我要说的是,这种形容不完全错,dg拥有备份的功能,某些情况下它甚至可以与primary数据库

一步一步学ZedBoard &amp; Zynq(七):制作ZedBoard上linux根文件系统(ramdisk)

一步一步学ZedBoard & Zynq(七):制作ZedBoard上linux根文件系统(ramdisk) 网址:http://xilinx.eetrend.com/blog/3935 Digilent的OOB设计给出了一个ZedBoard上完整的运行的linux系统所需要的所有文件,包括配置FPGA的bit文件. 配置ARM PS系统的First-Stage boot loader(FSBL)和引导linux需要的Second-Stage boot loader(SSBL).Linux内核z