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,一经查实,立即删除!

相关文章

raidrive安装失败_记一次RaiDrive映射OneDrive遇到的问题

大概在1周以前,出于需要存放直播录像的原因,根据别人的视频教程去自己动手搞了个5T网盘的帐号。(体验一下,其实我还同时存一份在百度云,怕不稳定)用RaiDrive创建OneDrive的映射,在这步骤点确定后,会弹出微软…

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

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

Javascript基础之-原型(prototype)

首先呢,prototype是对象里的一个内置属性,并且呢,这个属性是对于其他对象的一个引用。所以呢,思考下面的例子: var obj {a: 2 } var myObj Object.create(obj); console.log(myObj.a); // 2 console.log(myObj obj)…

Oracle查询今天、昨天、本周、上周、本月、上月数据

查询今天数据: SELECT COUNT(1) FROM T_CALL_RECORDS WHERE TO_CHAR(T_RKSJ,YYYY-MM-DD)TO_CHAR(SYSDATE,YYYY-MM-DD); 查询昨天数据: SELECT COUNT(1) FROM T_CALL_RECORDS WHERE TO_CHAR(T_RKSJ,YYYY-MM-DD)TO_CHAR(SYSDATE-1,YYYY-MM-DD)&…

usb一转多 树莓派zero_树莓派 Zero USB/以太网方式连接配置教程

树莓派 Zero 之所以成为一款非常棒的单板计算机并不全因为它小巧的尺寸和便宜的价格,还得益于它便捷、易用的特性。在加装了 Zero Quick Plug 或 microUSB/USB 转换头之后,将树莓派 Zero 和电脑连接起来。树莓派 Zero 即可配置成 USB/以太网设备&#xf…

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地址已经更换,很多教程里的地址是不对的 新地址安…

oppo的sd卡在哪里打开_oppo的sd卡在哪里打开

大家好,我是时间财富网智能客服时间君,上述问题将由我为大家进行解答。以oppo A91为例,其sd卡可直接在文件管理页面的存储里面即可打开。OPPO A91的屏幕为6.4英寸,主屏分辨率2400乘以1080像素,机身颜色有暗夜星辰&…

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

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

dg oracle 切换模式_Oracle数据库 DGbroker三种保护模式的切换

1.三种保护模式– Maximum protection在Maximum protection下, 可以保证从库和主库数据完全一样,做到zero data loss.事务同时在主从两边提交完成,才算事务完成。如果从库宕机或者网络出现问题,主从库不能通讯,主库也立…

软件包管理

应用程序:程序:Architecture C语言:源代码-->(编译) 二进制格式脚本:解释器(二进制程序) 源代码-->编译-->链接-->运行程序:指令数据指令:芯片CP…

工业机器人码垛教学实施_工业机器人应用案例码垛详解

工业机器人应用案例码垛详解随着科技的进步以及现代化进程的加快,人们对搬运速度的要求越来越高,传统的人工码垛只能应用在物料轻便、尺寸和形状变化大、吞吐量小的场合,这已经远远不能满足工业的需求,机器人码垛机应运而生。机器…

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

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

String与StringBuffer和StringBuilder的根本区别

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

16进制 ksh_AIX系统中如何统计进程打开的文件数目

作者:李烨楠 中国建设银行来自微信公众号:平台人生环境: AIX 6.1 AIX7.1前言:用户有时需要统计一个进程打开的文件数目,比如,在当前打开文件句柄使用量是否超过用户资源限制(/etc/security/limits)中 nofiles的取值时。那么&#…

前端Http协议缓存初解

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

详解java访问修饰符

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

educoder 二进制数据的位运算_二进制与位运算实用操作汇总(基础篇)

位运算是最高效而且占用内存最少的算法操作,但也是最难看懂的操作。然而,关于位运算的用法,笔者查了许多资料,似乎都没有找到详细而系统的讲解资料。笔者对位运算的操作相当感兴趣,因此斗胆尝试对位运算来一个的总结。…

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

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

白话说编程之java线程

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

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

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