热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java并发关键字,Java并发——关键字synchronized解析

synchronized用法在Java中,最简单粗暴的同步手段就是synchronized关键字,其同步的三种用法:①.同步实例方法,锁是

synchronized用法

在Java中,最简单粗暴的同步手段就是synchronized关键字,其同步的三种用法:

①.同步实例方法,锁是当前实例对象

②.同步类方法,锁是当前类对象

③.同步代码块,锁是括号里面的对象

示例:

public class SynchronizedTest {

/**

* 同步实例方法,锁实例对象

*/

public synchronized void test() {

}

/**

* 同步类方法,锁类对象

*/

public synchronized static void test1() {

}

/**

* 同步代码块

*/

public void test2() {

// 锁类对象

synchronized (SynchronizedTest.class) {

// 锁实例对象

synchronized (this) {

}

}

}

}

synchronized实现

javap -verbose查看上述示例:\

e9acd8f18a6c72cce2f24f4f9da5bc05.png

从图中我们可以看出:

同步方法:方法级同步没有通过字节码指令来控制,它实现在方法调用和返回操作之中。当方法调用时,调用指令会检查方法ACC_SYNCHRONIZED访问标志是否被设置,若设置了则执行线程需要持有管程(Monitor)才能运行方法,当方法完成(无论是否出现异常)时释放管程。

同步代码块:synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit两个字节码指令,每条monitorenter指令都必须执行其对应的monitorexit指令,为了保证方法异常完成时这两条指令依然能正确执行,编译器会自动产生一个异常处理器,其目的就是用来执行monitorexit指令(图中14-18、24-30为异常流程)。

具体看下monitorexit指令做了什么,在Hotspot源码中全文搜索monitorenter,在ciTypeFlow.cpp中找到如下:

case Bytecodes::_monitorenter:

{

pop_object();

set_monitor_count(monitor_count() + 1);

break;

}

case Bytecodes::_monitorexit:

{

pop_object();

assert(monitor_count() > 0, "must be a monitor to exit from");

set_monitor_count(monitor_count() - 1);

break;

}

void pop_object() {

assert(is_reference(type_at_tos()), "must be reference type");

pop();

}

void pop() {

debug_only(set_type_at_tos(bottom_type()));

_stack_size--;

}

int monitor_count() const { return _monitor_count; }

void set_monitor_count(int mc) { _monitor_count = mc; }

从源码中我们发现当线程获得该对象锁后,计数器就会加一,释放锁就会将计数器减一。

Monitor

每个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取该对象的监视器才能进入同步块和同步方法,如果没有获取到监视器的线程将会被阻塞在同步块和同步方法的入口处,进入到BLOCKED状态,如图:

6cc490e2da27c6bfc0ede9ab37ff0c91.png

Monitor是线程私有的数据结构,每个线程都有一个可用monitor record列表,同时 还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联(对象头的MarkWord中的LockWord指向monitor的起始地址),同时monitor中有一个owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。其结构如下:

b3563d497cc61e68e2f7a0ddc9469e35.png

Owner:初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL

EntryQ:关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record失败的线程

RcThis:表示blocked或waiting在该monitor record上的所有线程的个数

Nest:用来实现重入锁的计数

HashCode:保存从对象头拷贝过来的HashCode值(可能还包含GC age)

Candidate:用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降。Candidate只有两种可能的值0表示没有需要唤醒的线程1表示要唤醒一个继任线程来竞争锁\

锁优化

jdk1.6中synchronized的实现进行了各种优化,如适应性自旋、锁消除、锁粗化、轻量级锁和偏向锁,主要解决三种场景:

①.只有一个线程进入临界区,偏向锁

②.多线程未竞争,轻量级锁

③.多线程竞争,重量级锁

偏向锁→轻量级锁→重量级锁过程,锁可以升级但不能降级,这种策略是为了提高获得锁和释放锁的效率,源码解析可以看占小狼——synchronized实现

偏向锁

引入偏向锁的目的是:在没有多线程竞争的情况下,尽量减少不必要的轻量级锁执行路径。相对于轻量级锁,偏向锁只依赖一次CAS原子指令置换ThreadID,不过一旦出现多个线程竞争时必须撤销偏向锁,主要校验是否为偏向锁、锁标识位以及ThreadID。\

加锁

①.获取对象的对象头里的Mark Word

②.检测Mark Word是否为可偏向状态,即mark的偏向锁标志位为1,锁标识位为01

③.若为可偏向状态,判断Mark Word中的线程ID是否为当前线程ID,如果指向当前线程执行⑥,否则执行④

④.通过CAS操作竞争锁,竞争成功,则将Mark Word的线程ID替换为当前线程ID,否则执行⑤

⑤.通过CAS竞争锁失败,证明当前存在多线程竞争,当到达safepoint全局安全点(这个时间点是上没有正在执行的代码),获得偏向锁的线程被挂起,撤销偏向锁,并升级为轻量级,升级完成后被阻塞在安全点的线程继续执行同步代码块

⑥.执行同步代码块

\

解锁

线程是不会主动去释放偏向锁,只有当其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,释放锁需要等待全局安全点。步骤如下:

①.暂停拥有偏向锁的线程,判断锁对象石是否处于被锁定状态

②.撤销偏向锁,恢复到无锁状态(01)或者轻量级锁(00)的状态\

71c558908d6af5fc08ad0eb344d6a3ce.png

轻量级锁

引入轻量级锁的主要目的是在多线程没有竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。如果多个线程在同一时刻进入临界区,会导致轻量级锁膨胀升级重量级锁,所以轻量级锁的出现并非是要替代重量级锁,在有多线程竞争的情况下,轻量级锁比重量级锁更慢。

加锁

①.获取对象的对象头里的Mark Word

②.判断当前对象是否处于无锁状态,即mark的偏向锁标志位为0,锁标志位为 01

③.若是,JVM首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝(官方把这份拷贝加了一个Displaced前缀,即Displaced Mark Word),然后执行④;若不是执行⑤

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

⑤.判断当前对象的Mark Word是否指向当前线程的栈帧,如果是说明当前线程已经持有这个对象的锁,则直接执行同步代码块;否则说明该锁对象已经被其他线程抢占了,如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,锁标志位变成10,后面等待的线程将会进入阻塞状态

\

解锁

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

①.如果对象的Mark Word仍然指向着线程的锁记录,执行②

②.用CAS操作把对象当前的Mark Word和线程中复制的Displaced Mark Word替换回来,如果成功,则说明释放锁成功,否则执行③

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

1278023e1e313e3cff5240e174dea242.png

重量级锁

重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。

感谢

芋道源码——synchronized实现原理

《深入理解Java虚拟机》

《java并发编程的艺术》



推荐阅读
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • PHP中的单例模式与静态变量的区别及使用方法
    本文介绍了PHP中的单例模式与静态变量的区别及使用方法。在PHP中,静态变量的存活周期仅仅是每次PHP的会话周期,与Java、C++不同。静态变量在PHP中的作用域仅限于当前文件内,在函数或类中可以传递变量。本文还通过示例代码解释了静态变量在函数和类中的使用方法,并说明了静态变量的生命周期与结构体的生命周期相关联。同时,本文还介绍了静态变量在类中的使用方法,并通过示例代码展示了如何在类中使用静态变量。 ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 从零学Java(10)之方法详解,喷打野你真的没我6!
    本文介绍了从零学Java系列中的第10篇文章,详解了Java中的方法。同时讨论了打野过程中喷打野的影响,以及金色打野刀对经济的增加和线上队友经济的影响。指出喷打野会导致线上经济的消减和影响队伍的团结。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
author-avatar
蓝雪帝国666
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有