`
dsxwjhf
  • 浏览: 73690 次
  • 性别: Icon_minigender_1
  • 来自: 安徽
社区版块
存档分类
最新评论

读书笔记 ------ 高效并发之轻量级锁

阅读更多
我们知道, Java 虚拟机堆上的对象,除了存储实体数据外,还有个对象头的概念。它存储了诸如 hashCode 、 GC 分代年龄、指向类的指针等信息。对象头中的数据大部分都不是必须的,但却能帮助虚拟机更方便、快捷地完成某些功能。比如“指向类的指针”,就可以很方便地实现反射(即使没有这个指针,虚拟机也能完成反射,但可能就会麻烦很多)。

正因为“不重要”,对象头的数据结构非常灵活,在对象处于不同状态时,根据需要存储不同的东西。比如对象未被锁定时,可以存储哈希码、 GC 分代年龄等,此时把自己的状态标志设为1;对象被轻量级锁定时,存储指向轻量级锁的指针,此时把自己的状态标志设为0;对象处于重量级锁定时,存储指向操作系统互斥量的指针,把自己的状态标志设为2,等等。

那轻量级锁又是什么呢?我们知道,为了保证共享数据的安全,我们不得不使用 synchronized 同步块之类的手段来保证共享数据的安全。但是同步通常意味着线程的阻塞,涉及到线程的挂起和唤醒,而 Java 中的线程是映射到操作系统的物理线程上的,这些挂起和唤醒操作需要程序不停地在用户态和内核态之间切换,这是一笔不小的开销。为了保证并发正确,给共享数据加锁的操作是不可避免的,但是在实际情况下,又有多少机会说好几条线程真的会在同一时间进入到相同的同步块呢?好像很少。。悲哀的是,即便只有一条线程,虚拟机还不得不执行加锁、解锁、维护锁计数器、用户态-内核态切换等等操作(好吧,只有一条线程的时候,不存在阻塞的问题,但加锁和解锁还是必须的,如果不做任何优化的话,加锁和解锁要使用操作系统的互斥量,所以还是得切换到内核态)。所以对于这种情况有没有优化的余地?轻量级锁就是为了解决这个问题被发明出来的。

轻量级锁的原理和执行过程,这里就不再抄书了。可以参考 虚拟机中的锁优化简介(适应性自旋/锁粗化/锁削除/轻量级锁/偏向锁) 这篇文章,作者是周志明大大本人。

这里面有这么一句话:如果这个更新操作失败了,虚拟机首先会检查对象的 Mark Word 是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明这个锁对象已经被其他线程抢占了。开始的时候很疑惑,既然 CAS 操作都失败了, Mark Word 怎么可能有指向 Lock Record 的指针呢?其实这句话是针对第二次、第三次、第 N 次加锁过程来说的,第一次加锁过程确实不会有这种情况。还有一点, CAS 操作比较的是什么?加锁的时候,比较的是对象的 Mark Word 和栈上的 Mark Word ;解锁的时候,估计是预先把刚刚加锁后的 Mark Word 记下来了,然后和当前的 Mark Word 比较。

最后再抄一点书,关于偏向锁。
  偏向锁也是 JDK 1.6 中引入的一项锁优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。如果说轻量级锁是在无竞争的情况下使用 CAS 操作去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下把整个同步都消除掉,连 CAS 操作都不做了。
  偏向锁的“偏”,就是偏心的“偏”、偏袒的“偏”。它的意思是这个锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。
    偏向锁在对象头中记录了获取到该锁的线程的 ID 。



补:关于 CAS 过程的修正

CAS 的语义是: CAS 有3个操作数,内存值 V ,旧的预期值 A ,要修改的新值 B 。当且仅当预期值 A 和内存值 V 相同时,将内存值 V 修改为 B ,否则什么都不做。所以,我的猜测是:

加锁过程:在 CAS 之前, Lock Record 的 owner 指针已经指向对象的 Mark Word 了。 CAS 操作判断对象当前的 Mark Word 和栈上刚刚复制过来的 Mark Word 是否相等,若相等修改对象的 Mark Word 为指向 Lock Record 的指针。若该 CAS 操作成功,则本线程成功获得锁,接着把 Object 锁标志位标记为“00”(轻量级锁定);否则检查对象 Mark Word 的指针是否指向当前线程的 Lock Record ,若是说明当前线程已经获得了锁,本次加锁是一个重入过程,可以进入同步块直接执行;否则说明该锁被其它线程抢占了(就在复制 Mark Word 和设置 owner 指针期间。可能我先开始,但别人更快),于是修改对象的锁标志为重量级锁定,并把 Mark Word 的锁指针修改为指向操作系统互斥量,对象膨胀为重量级锁,本线程进入阻塞状态。

解锁过程:和加锁类似。稍有不同的是,这次比较的是栈上 Mark Word 的地址和对象 Mark Word 指针所指向的地址是否一样。若不一样只有一种解释,别的线程在这段时间之内申请过该锁,对象膨胀成了重量锁,这时候需要唤醒被阻塞的线程。

if (header.flag == 00) {
    assign space on stack frame: Lock_Record;
	Lock_Record.header <== object.header;
	Lock_Record.pointer --> object.header;
	
	success <== CAS(object.header, Lock_Record.header, object.header.pointer --> Lock_Record and object.header.flag <== 01);
	
	if (success) {
	    OK;
	} else {
	    reenter <== object.header.pointer --> Lock_Record ??
		if (reenter) {
		    OK;
		} else {
		    object.header.flag <== 02;
			blocked;
		}
	}
} else if (header.flag == 01) {
    object.header.flag <== 02;
    blocked;
} else if (header.flag == 02) {
    blocked;
}
分享到:
评论

相关推荐

    ARM嵌入式系统技术笔记-基于LPC2300上

    可能会讨论FreeRTOS这样的轻量级RTOS在LPC2300上的移植和应用,以及如何利用RTOS来管理和调度任务,提高系统的并发性和响应速度。 在软件开发方面,读者可以学习到C语言编程技巧,特别是在嵌入式环境下的优化方法。...

    笔记-5、并发容器2

    Segment是一种可锁的类,类似于一个轻量级的HashTable。每个Segment管理一部分Entry,通过位运算来确定Entry属于哪个Segment。Segment内部使用了链地址法来处理哈希冲突,即相同哈希值的元素会被链接在一起形成链表...

    java并发编程实践pdf笔记

    - **使用并发原语** 例如`java.util.concurrent.atomic`包中的原子变量,可以提供轻量级的线程安全。 通过这些笔记,我们可以了解到Java并发编程中的关键概念和实践,对于理解和编写高效的并发代码具有极大的帮助...

    go学习笔记-文档-文档

    Go语言的并发编程是其一大特色,它通过goroutine(轻量级线程)和channel来实现。goroutine是一种低成本的并发执行单元,启动简单,只需要在函数调用前加上`go`关键字。Channel则充当goroutine间的通信桥梁,允许...

    renren-security轻量级权限管理系统 v5.3.0.zip

    "renren-security轻量级权限管理系统 v5.3.0.zip" 是一个包含全面权限管理功能的软件包,主要用于帮助开发者构建安全、高效的企业级应用。这个系统以其轻量级和高可定制性为特点,提供了灵活的角色权限分配、用户...

    java学习笔记-html-ssh-js

    - **Spring**:轻量级框架,提供依赖注入、AOP(面向切面编程)和MVC(模型-视图-控制器)模式。 - **Hibernate**:持久化框架,简化数据库操作,实现对象-关系映射。 - **Struts**:MVC框架,处理用户请求并转发...

    并发编程之一 日常学习笔记

    3. 如果CAS失败,表示有其他线程竞争,此时轻量级锁会升级为重量级锁,等待的线程会被挂起,直到持有锁的线程释放锁。 4. 释放锁时,会检查是否有等待的线程,如果有,则唤醒等待线程,否则将锁恢复为轻量级锁。 ...

    分布式锁与信号量-2023C-mas开发笔记

    而Redis和Zookeeper的锁则更加轻量级,但需要额外的运维支持。 接下来,我们讨论信号量。信号量是一种更为宽松的同步原语,它允许一定数量的线程同时访问共享资源。在分布式系统中,信号量可以看作是一种计数器,当...

    golang-china读书笔记

    - **Goroutines**: 是轻量级的线程,可以轻松创建数千甚至数百万个goroutine。 - **Channels**: 用于goroutine之间的通信,提供安全的数据交换机制。 - **并行的循环**: 展示了如何使用goroutine和channel来实现并行...

    自我学习笔记--LUA;python:网络编程等

    LUA是一种轻量级的解释型脚本语言,其设计目标是简洁、快速和可嵌入。它的语法简单,易于学习,且在游戏开发中尤为常见,如COCOS2D-X引擎就大量使用LUA。在LUA中,网络编程可能涉及到socket库,用于创建客户端和...

    linux笔记-01安装与教程

    - **资源占用少**:对于资源有限的嵌入式系统而言,Linux提供了轻量级的解决方案。 - **社区支持**:庞大的开发者社区为Linux提供了强大的技术支持和丰富的资源。 #### 四、Ubuntu安装与文件管理 - **安装指南**:...

    OpenEjb使用笔记--让Tomcat可以部署EJB

    本文将详细解析如何使用OpenEjb使Tomcat能够支持EJB的部署,这对于那些希望在轻量级应用服务器如Tomcat上运行企业级Java组件的开发者来说是非常有价值的。 首先,我们要理解EJB是什么。EJB是Java EE平台中的核心...

    Java并发编程学习笔记.rar

    它是Java中轻量级的同步机制。 4. **线程局部变量**:`ThreadLocal`类用于为每个线程提供独立的变量副本,避免了线程间的数据共享问题,提升了并发性能。 5. **并发集合**:`java.util.concurrent`包提供了线程...

    研究生《操作系统-汤子瀛》读书笔记

    - **线程**:进程内的轻量级实体,共享进程资源。 **9. 共享性** - **互斥共享**:同一时刻只有一个进程使用资源。 - **同时访问**:允许多个进程同时访问资源。 **10. 虚拟技术** - **时分复用**:通过时间划分...

    SQLite开发笔记3(在arm-linux平台上建立嵌入式C数据库)

    ### SQLite在ARM-Linux平台上的嵌入式C数据库构建指南 #### 一、概述 本文档旨在指导读者如何在ARM-Linux平台上...这对于嵌入式系统的开发人员来说非常有用,因为它为存储和管理数据提供了轻量级且高效的解决方案。

    Swing学习系列 -- Swing读书笔记目录

    首先,Swing是Java Foundation Classes (JFC)的一部分,它是Sun Microsystems为Java平台提供的一个轻量级组件库。Swing提供了丰富的组件,如按钮、文本框、面板、菜单等,使得开发者能够创建功能强大的、具有吸引力...

    考研复习知识笔记-操作系统

    而线程是轻量级的进程,它们共享进程的地址空间,线程间的同步和通信更为方便,因此引入线程可以减少并发执行时的时空开销,提高操作系统的并发性能。 进程间通信(IPC)是操作系统中必不可少的部分,通常通过共享...

    马士兵百万级并发IM即时消息系统

    - 并发模型:Go语言的goroutine和channel提供了轻量级线程和数据同步机制,适合处理大量并发连接。 - 内存管理:Go的垃圾回收机制减少了开发者对内存管理的担忧,同时也保证了程序的稳定性。 - 静态链接和C级性能...

    网络工程师笔记-操作系统基础

    线程是进程内的执行单元,是处理器分配的最小单位,相比进程更轻量级,能提高系统并发性。在多线程环境中,进程间的交互和协作通过互斥和同步机制来实现。互斥是指资源在同一时刻只能被一个线程使用,而同步则涉及到...

Global site tag (gtag.js) - Google Analytics