Java并发编程之synchronized关键字解析

前言

        公司加班太狠了,都没啥时间充电,这周终于结束了。这次整理了Java并发编程里面的synchronized关键字,又称为隐式锁,与JUC包中的Lock显示锁相对应;这个关键字从Java诞生开始就有,称之为重量级锁,自从JDK1.6之后官方对该关键字进行优化,引入了轻量级锁和偏向锁,于是就有了锁升级的概念。

使用

在代码中使用这个关键字总共有以下三种:

    private static Object object = new Object();private synchronized void function1() {//锁住当前实例对象}private static synchronized void function2() {//锁住当前class对象,可以认为是锁住当前Class文件}public static void main( String[] args ) {synchronized (object) {//锁住object对象}}

1:普通方法同步;

2:静态方法同步;

3:同步代码块括号中的对象;

使用synchronized关键字进行同步,则锁是储存于Java对象头里面的Mark Word。

Java对象头里面的Mark Word里面主要是存储对象的hashCode、分代年龄(用于判断为老年代还是年轻代,在垃圾回收器里面用得到)以及锁标记位。

锁的升级

在JDK1.6之后,引入了引入了偏向锁和轻量级锁的状态,目的是为了提升锁的释放和获取效率,减少性能的开销,所以synchronized就有四种级别锁的状态,级别从低到高分别是无锁状态、偏向锁状态、轻量级锁以及重量级锁状态;四种状态实质上是对象头储存的锁标记位不一致,使用CAS更改对象头的标记位进行锁状态位;并且在记录锁的标记位的同时,也会在Mark Word里面记录锁线程的ID

无锁

既在对象头的Mark Word没有标记锁状态的时候就是无锁状态

偏向锁

单个线程进行访问或调用带synchronized的同步代码块或方法时,会在先判断对象头里面锁标志位是否有线程ID,如果没有线程ID的话,将当前线程ID写入进去,并且也会在栈帧中的锁记录里面进行记录。此时,锁的状态位为偏向锁,通俗来说可以说是只要单线程访问同步代码块,从无锁状态就会便成为偏向锁状态;如果在对象头里面存在线程ID的话,如果当前线程ID是与对象头里面记录的线程ID的话,那么就可以直接访问,不需要使用CAS去进行竞争锁了;不一致的话,那么就会使用CAS进行竞争锁。

当其他线程开始竞争偏向锁的时候,那么持有偏向锁的线程就去会释放偏向锁,供其他线程使用;这时候就出现一种偏向锁的撤销概念

偏向锁的撤销

偏向锁的撤销就是,会先暂停持有偏向锁的线程,然后去对象头里面判断记录线程ID的线程是否还在处于活动状态,如果处于非活动状态,那么就会将Mark Word里面的锁标志位设置为无锁状态;如果处于活动状态的话,会先遍历偏向对象的锁记录、栈里面锁记录以及对象头里面Mark Word里面的锁标记位,并且将对象头中锁标志位设置为无锁状态或者升级成轻量锁状态的,然后唤醒持有偏向锁的的线程,继续执行;

轻量级锁

加锁

当执行同步代码块升级为轻量级锁的时候,会在栈帧中创建一块用于储存锁记录的空间,并且将对象头里面Mark Word复制到记录中(Displaced Mark Word),线程开始会使用CAS将Mark Word替换为指向锁记录的指针,如果获取成功,那么当前线程获取锁,如果失败,采用自旋来获取锁,如果自旋获取锁失败,那么会膨胀为重量级锁。

解锁

轻量级锁解锁会使用CAS将Displaced Mark Word替换回到对象头中,如果成功了,表示没有锁竞争;如果失败了表示有锁在竞争,那么就会膨胀成重量级锁,那么在自旋的获取锁的线程就会进行线程阻塞;

由于自旋会大量消耗CPU资源,所以一旦升级成为了重量级锁之后,那么就不会进行降级了。

重量级锁

这个就是线程阻塞了,基本上可以和Lock表现一致了,一旦有线程获取锁,其他获取锁的线程将会阻塞,释放锁之后将会唤醒阻塞线程去竞争锁

优点缺点使用场景
偏向锁加锁和解锁不需要额外的资源消耗,性能快如果线程之间存在锁竞争,那么会出现锁撤销,暂停线程,比较消耗资源适用于单线程使用场景
轻量级锁线程不会阻塞,提高响应程度自旋消耗CPU性能,容易升级为重量级锁适用于同步代码块执行非常快的
重量级锁线程不会自旋,避免过多消耗CPU资源线程阻塞,响应时间缓慢提高吞吐量,同步代码块执行较长

等待/通知机制

这个之前是有篇讲过线程之间的共享协作:线程协作 这个里面提到过如何使用

现在看看底层是如何运行的执行,我们先看一段代码

public class SynchronziedDemo {public static Object object = new Object();public static void main(String[] args) {synchronized (object) {}m();}public static synchronized void m() {}
}

将这段代码编译后使用javap -v命令进行反编译

会得到class文件的编译后的c代码:

同步代码块使用的事monitorenter(获取锁)和monitorexit(释放锁)指令,同步放上是使用ACC_SYNCRONIZED来完成的。

无论是哪个本质上其实是个monitor监视器进行完成的,每个对象都会有一个监视器,线程需先获取到monitor监视器才能访问同步代码块或者方法,而没有获取到的线程就会进入自旋,升级为重量级锁,然后会进入到阻塞状态,这时候会有一个同步队列(SynchronizedQueue),阻塞的线程会加入到这个队列里面,等待获取到监视器的线程调用monitorexit指定后,会唤醒同步队列里面的等待线程。

等待/通知机制里面对了一个WaitQueue即等待队列,案例:


public class WaitNotify {public static Object object = new Object();public static void main(String[] args) {new Thread(new WaitClass()).start();new Thread(new NotifyClass()).start();}static class NotifyClass implements Runnable {@Overridepublic void run() {synchronized (object) {try {TimeUnit.SECONDS.sleep(2L);System.out.println(Thread.currentThread() + "notify start...");object.notifyAll();System.out.println(Thread.currentThread() + "notify end...");} catch (InterruptedException e) {e.printStackTrace();}}}}static class WaitClass implements Runnable {@Overridepublic void run() {synchronized (object) {System.out.println(Thread.currentThread() + "wait start....");try {object.wait();System.out.println(Thread.currentThread() + "wait end....");} catch (InterruptedException e) {e.printStackTrace();}}}}
}

 打印日志如下:

Thread[Thread-0,5,main]wait start....
Thread[Thread-1,5,main]notify start...
Thread[Thread-1,5,main]notify end...
Thread[Thread-0,5,main]wait end....

对于wait线程获取到object对象的监视器之后,调用wait方法后进入等待队列(WaitQueue),然后释放监视器。

对于notify线程来说,先获取到object对象监视器之后,然后调用notifyAll方法,将WaitQueue里面的所有等待线程同步到同步队列中,(如果是调用notify方法就会值同步一个线程并非所有线程),然后释放监视器,就会唤醒同步列队中的线程。

对于wait线程,在同步队列呗唤醒后,会重新获取监视器,然后继续执行wait方法后面的代码。

这样就完成一个等待通知的机制了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/537090.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

通过代理模式 + 责任链模式实现对目标执行方法拦截和增强功能

前言 最近需要实现一个插件功能,但是如果做成两个接口的话(即执行前和执行后),那么会降低插件的可玩性,所以需做成类似AOP的环绕通知形式,所以就使用到了责任链模式和代理模式进行实现。 介绍 代理模式(P…

vscode Go 1.11.4 编译错误 need Delve built by Go 1.11 or later

更新golang的版本为1.11.4之后vscode编译错误:executables built by Go 1.11 or later need Delve built by Go 1.11 or later 原因是delve的版本太老了,需要更新,且delve的github地址已经更换,很多教程里的地址是不对的 新地址安…

Navicat使用教程:使用Navicat Query Analyzer优化查询性能(第1部分)

下载Navicat Monitor最新版本Navicat Monitor 是一套安全、简单而且无代理的远程服务器监控工具。它具有强大的功能使你的监控发挥最大效用。受监控的服务器包括 MySQL、MariaDB 和 Percona Server,并与 Amazon RDS、Amazon Aurora、Oracle Cloud、Microsoft Azure …

第一家云创大数据产业学院在佛山职业技术学院挂牌

2019年1月10日,“云创大数据产业学院揭牌暨战略合作协议签署仪式”在佛山职业技术学院电子信息学院会议室举行。云创大数据总裁刘鹏教授、市场部经理单明月,佛山职业技术学院电子信息学院院长唐建生、副院长田钧、学院办公室主任赵雪章、信息工程系主任乔…

String与StringBuffer和StringBuilder的根本区别

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

前端Http协议缓存初解

[TOC] 简介 用户获取网络资源,需要通过非常长的网络去服务器上请求资源,另外服务端为了应对大量的用户请求而不断的提升硬件性能与带宽。这对用户与服务端都非常的不友好。而缓存就是为了解决用户请求速度与释放服务器压力而生的。 为什么我会写Http缓存&#xff0c…

详解java访问修饰符

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

企业为什么要做SEO,它的重要性有哪些?

对于SEO工作而言,我们知道一个网站做SEO的基础诉求就是让用户和搜索引擎更好的理解网站内容,虽然随着搜索引擎算法技术的迭代,目前SEO面临更大的挑战与竞争,但基于搜索营销,它目前仍然显得十分重要。 那么&#xff0…

白话说编程之java线程

白话说编程之java线程线程和进程:进程:线程:线程和进程的区别:详解多线程:并发为什么使用并发并发的执行原理并行线程的五种状态:创建状态:就绪状态:运行状态:阻塞状态:死…

powerdesigner显示工具面板_photoshop教程-画笔工具预设与选项设置

定义画笔预设在打开的“画笔”面板中,单击左侧的“画笔笔尖形状”名称,可显示笔尖形状图案。单击“画笔”面板左侧其他不同的选项名称,在右侧就会显示其对应的调节项。只单击不同选项前面的方框,可使此选项有效,但右侧…

深入理解== 和 equals 的区别

深入理解 和 equals 的本质区别简介区别:图解:注意点:源码分析:总结分享一波:程序员赚外快-必看的巅峰干货简介 初学者常常被" “和‘equals ’所折磨,为什么,因为他们的大概意思相同,都是…

java sleep和wait区别

为什么80%的码农都做不了架构师?>>> 关于sleep和wait区别解析: sleep只是释放CPU资源,并不释放资源锁对象,wait是会释放掉资源锁对象。 比如,有个锁对象object,线程1和线程2都会锁住object对象…

深入理解equals和hashCode关系和区别

深入理解equals和hashCode关系和区别直入主题:区别:1.他们判断对象相同的方式不一样:2.他们判断对象是否相等的准确率不一样:改写equals时总是要改写hashcode分享一波:程序员赚外快-必看的巅峰干货为什么要说equals和hashCode这两…

Jdk 和 jre 的 关系和区别

Jdk 和 jre 的 关系和区别 区别: JDK:是Java Development Kit 的简称–>翻译过来就是:Java 开发工具包。是程序员使用java语言编写java程序所需的开发工具包,是提供给程序员使用的。 JRE:是Java Runtime Environm…

OpenCV-Python入门教程7-PyQt编写GUI界面

前面一直都是使用命令行运行代码,不够人性化。这篇用Python编写一个GUI界面,使用PyQt5编写图像处理程序。包括:打开、关闭摄像头,捕获图片,读取本地图片,灰度化和Otsu自动阈值分割的功能。 使用Qt Designer…

spark 广播变量大数据_大数据处理 | Spark集群搭建及基本使用

点击蓝字关注我前面用了一篇文章详细的介绍了集群HDFS文件系统的搭建,HDFS文件系统只是一个用于存储数据的系统,它主要是用来服务于大数据计算框架,例如MapReduce、Spark,本文就接着上一篇文章来详细介绍一下Spark集群的搭建及Spa…

如何将本地项目上传到gitee

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

oracle dg 备库未设置convert参数导致ORA-01111,ORA-01110

2019独角兽企业重金招聘Python工程师标准>>> 查看trace 文件: MRP0: Background Managed Standby Recovery process started (amls) started logmerger process Sun Jan 20 07:55:53 2019 Managed Standby Recovery starting Real Time Apply MRP0: Back…

VMware安装虚拟机并使用NAT模式连接网络

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…