面试必问的Synchronized知道这些就可以了
Synchronized关键字算是Java的元老级锁了,一开始它撑起了Java的同步任务,其用法简单粗暴容易上手。但是有些与它相关的知识点还是需要我们开发者去深入掌握的。比如,我们都知道通过Synchronized锁来实现互斥功能,可以用在方法或者代码块上,那么不同用法都是怎么实现的,以及都经历了了哪些优化等等问题都需要我们扎实的理解。
1.基本用法
通常我们可以把Synchronized用在一个方法或者代码块里,方法又有普通方法或者静态方法。
对于普通同步方法,锁是当前实例对象,也就是this
public class TestSyn{
private int i=0;
public synchronized void incr(){
i++;
}
}
对于静态同步方法,锁是Class对象
public class TestSyn{
private static int i=0;
public static synchronized void incr(){
i++;
}
}
对于同步代码块,锁是同步代码块里的对象
public class TestSyn{
private int i=0;
Object o = new Object();
public void incr(){
synchronized(o){
i++;
}
}
}
2.实现原理
在JVM规范中介绍了synchronized的实现原理,JVM基于进入和退出Monitor对
象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步是使用另外一种方式实现的,通过一个方法标志(flag) ACC_SYNCHRONIZED来实现的。
2.1 同步代码块的实现
monitorenter 和 monitorexit
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.monitorenter (参考来源)
下面看下JVM规范里对moniterenter 和 monitorexit的介绍
Each object has a monitor associated with it. The thread that executes monitorenter gains ownership of the monitor associated with objectref. If another thread already owns the monitor associated with objectref, the current thread waits until the object is unlocked,
每个对象都有一个监视器(Moniter)与它相关联,执行moniterenter指令的线程将获得与objectref关联的监视器的所有权,如果另一个线程已经拥有与objectref关联的监视器,则当前线程将等待直到对象被解锁为止。
A monitorenter instruction may be used with one or more monitorexit instructions to implement a synchronized statement in the Java programming language. The monitorenter and monitorexit instructions are not used in the implementation of synchronized methods
重点来了,上面这段介绍了两点:
- 通过monitorenter和monitorexit指令来实现Java语言的同步代码块(后面有代码示例)
- monitorenter和monitorexit指令没有被用在同步方法上!!!
2.2 同步方法的实现
先看下JVM规范里怎么说的
https://docs.oracle.com/javase/specs/jvms/se6/html/Compiling.doc.html#6530 (参考来源)
A synchronized method is not normally implemented using monitorenter and monitorexit. Rather, it is simply distinguished in the runtime constant pool by the ACC_SYNCHRONIZED flag, which is checked by the method invocation instructions. When invoking a method for which ACC_SYNCHRONIZED is set, the current thread acquires a monitor, invokes the method itself, and releases the monitor whether the method invocation completes normally or abruptly.
上面这段话主要讲了几点:
- 同步方法的实现不是基于monitorenter和monitorexit指令来实现的
- 在运行时常量池里通过ACC_SYNCHRONIZED来区分是否是同步方法,方法执行时会检查该标志
- 当一个方法有这个标志的时候,进入的线程首先需要获得监视器才能执行该方法
- 方法结束或者抛异常时会释放监视器
public class TestSyn {
private int i=0;
// 同步方法
public synchronized void incer(){
i++;
}
// 同步代码块
public void decr(){
synchronized (this) {
i--;
}
}
}
可以通过反编译字节码来查看底层是怎么实现的
// 得到字节码
javac TestSyn.java
// 反编译字节码
javap -v TestSyn.class
同步代码块的反编译结果如下:
同步方法的反编译结果如下:
3.锁升级
3.1 Java对象头介绍
对象的内存布局
在我们常见的HotSpot虚拟机中对象由三部分组成,分别是对象头,实例数据,以及对齐填充位。其中对象头是跟锁信息相关的部分,在对象头里会存储该对象运行时数据,包括哈希吗,GC分代年龄,锁状态(无锁,偏向锁,轻量级锁,重量级锁),是否偏向锁,偏向线程ID等信息。
存储上述这些的区域叫做Mark Word(标记词),除了这部分对象头还有一部分区域用来存储类型指针,可以通过该类型指针来定位对象的元数据信息。下面重点看下,对象头的内存布局,因为这部分是跟我们这次相关的。
对象在内存中的表示如下图:
对象头的结构表示如下图:
mark word的表示如下图:
3.2 什么是锁升级
下面举个抢茅坑的例子来解释一下锁升级过程。
当只有一个线程访问时叫做偏向锁
假设我们每个厕所都有一把钥匙,要想使用厕所首先必须得获得锁。某天上午员工甲急急忙忙的打完卡上厕所了,并在厕所门上贴了 “工号007使用中”的标签,说明目前被工号007(相当于线程id)的员工占用呢,他再次向进入的时候只要上面的标签还显示工号007,他自己可以随便进入,不需要再次上锁了,有点偏向工号007员工的意思,所以这叫偏向锁。
发生竞争的时候升级成轻量级锁 (自旋等待)
员工甲正在使用厕所的时候,又来了两个人想用厕所,但发现厕所被人使用着呢,无法获得锁。所以只能在外面等着甲出来,他们等的过程叫做“自旋”,这个叫做轻量级锁。那么又有一个问题,当甲出来之后正等着的那两个人谁活得锁呢?有两种方式,按到达的顺序来排队或者不排队,这两种都可以实现,前者叫做公平锁,后者叫做非公平锁。
自旋等待没结果的时候升级成重量级锁
但那两个人自旋一段时间之后发现甲还没出来(JDK1.6规定为10次),一直这么等也不是个法子啊,所以打算向上升级,找厕所管理员(操作系统)反馈,升级成了重量级锁了
锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁。
锁升级过程中mark word的变化如下:
偏向锁
偏向锁也是JDK 1.6中引入的一项锁优化, 引入它是为了优化在没有锁竞争场景下的锁消除。比如一段同步代码一直是由单个线程调用,在这种场景下就没必要使用同步锁了,这里指的同步锁不是指synchronized,而是说没不要到操作系统层面的互斥量了。
偏向锁的偏向是指该同步代码会一直偏向第一个调用它的线程,直到有别的线程过来竞争这把锁,在第一次调用同步代码并获得锁时会在对象头和栈帧锁记录行(Lock Record)里存储偏向线程Id,该线程在此进入的时候就不需要重新申请锁了。只需检测对象头的Mark Word里是否存储着指向该线程的ID即可。
直到又有线程来竞争这把锁的时候偏向锁会撤销偏向。
轻量级锁
轻量级锁是JDK 1.6之中加入的新型锁机制, 它名字中的“轻量级”是相对于使用操作系统
互斥量来实现的传统锁而言的, 因此传统的锁机制就称为“重量级”锁。 它并不是用来代替重量级锁的, 它的本意是在统的重量级锁使用操作系统互斥量产生的性能消耗。
线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁.一直原地自旋,如果自旋数达到10次了则升级为重量级锁。
重量级锁
竞争的线程自旋一段时间未能获取锁之后会升级为重量级锁,这个时候锁的获取与释放都会由操作系统来分配了,如果持有锁的线程释放锁之后操作系统会唤醒所有阻塞的哪些线程,并进入新一轮的争抢模式,需要注意的是这些阻塞的线程没有获得锁的优先级,也就是说synchronized锁是非公平的。除此之外synchronized对中断操作也是无感的,不会因为被中断而放弃阻塞等待,它要么得到锁要么一直阻塞。
面试必问的Synchronized知道这些就可以了的更多相关文章
- linux驱动工程面试必问知识点
linux内核原理面试必问(由易到难) 简单型 1:linux中内核空间及用户空间的区别?用户空间与内核通信方式有哪些? 2:linux中内存划分及如何使用?虚拟地址及物理地址的概念及彼此之间的转化, ...
- 互联网公司面试必问的mysql题目(上)
又到了招聘的旺季,被要求准备些社招.校招的题库.(如果你是应届生,尤其是东北的某大学,绝对福利哦) 介绍:MySQL是一个关系型数据库管理系统,目前属于 Oracle 旗下产品.虽然单机性能比不上or ...
- 互联网公司面试必问的Redis题目
Redis是一个非常火的非关系型数据库,火到什么程度呢?只要是一个互联网公司都会使用到.Redis相关的问题可以说是面试必问的,下面我从个人当面试官的经验,总结几个必须要掌握的知识点. 介绍:Redi ...
- 【面试必问】python实例方法、类方法@classmethod、静态方法@staticmethod和属性方法@property区别
[面试必问]python实例方法.类方法@classmethod.静态方法@staticmethod和属性方法@property区别 1.#类方法@classmethod,只能访问类变量,不能访问实例 ...
- 互联网公司面试必问的mysql题目(下)
这是mysql系列的下篇,上篇文章地址我附在文末. 什么是数据库索引?索引有哪几种类型?什么是最左前缀原则?索引算法有哪些?有什么区别? 索引是对数据库表中一列或多列的值进行排序的一种结构.一个非常恰 ...
- 面试必问:JVM类加载机制详细解析
前言 在Java面试中,简历上有写JVM(Java虚拟机)相关的东西,JVM的类加载机制基本是面试必问的知识点. 类的加载和卸载 JVM是虚拟机的一种,它的指令集语言是字节码,字节码构成的文件是cla ...
- 一线大厂Java面试必问的2大类Tomcat调优
一.前言 最近整理了 Tomcat 调优这块,基本上面试必问,于是就花了点时间去搜集一下 Tomcat 调优都调了些什么,先记录一下调优手段,更多详细的原理和实现以后用到时候再来补充记录,下面就来介绍 ...
- python笔记39-unittest框架如何将上个接口的返回结果给下个接口适用(面试必问)
前言 面试必问:如何将上个接口的返回结果,作为下个接口的请求入参?使用unittest框架写用例时,如何将用例a的结果,给用例b使用. unittest框架的每个用例都是独立的,测试数据共享的话,需设 ...
- 高级测试工程师面试必问面试基础整理——python基础(一)(首发公众号:子安之路)
现在深圳市场行情,高级测试工程师因为都需要对编程语言有较高的要求,但是大部分又没有python笔试机试题,所以面试必问python基础,这里我整理一下python基本概念,陆续收集到面试中python ...
随机推荐
- C++基础之适配器
什么是容器适配器? ”适配器是使一种事物的行为类似于另外一种事物行为的一种机制”,适配器对容器进行包装,使其表现出另外一种行为.例如,stack<int, vector<int> & ...
- Transformer各层网络结构详解!面试必备!(附代码实现)
1. 什么是Transformer <Attention Is All You Need>是一篇Google提出的将Attention思想发挥到极致的论文.这篇论文中提出一个全新的模型,叫 ...
- SpringBoot起飞系列-使用idea搭建环境(二)
一.环境配置 安装idea的教程就不说了,相信大家肯定已经安装好了,另外maven环境肯定也安装好了,那么我们就开始使用idea开发工具来创建一个springboot的web项目,这里奉上一个idea ...
- 2015年蓝桥杯java b组第十题
10. 生命之树 在X森林里,上帝创建了生命之树. 他给每棵树的每个节点(叶子也称为一个节点)上,都标了一个整数,代表这个点的和谐值. 上帝要在这棵树内选出一个非空节点集S,使得对于S中的任意两个点a ...
- linux系统下开发环境安装与配置
安装系统环境 CentOS 6.8 64位 jdk版本 7u80 64位 Tomcat版本 Tomcat7 maven版本 Apache Maven 3.6.0 vsftpd版本 vsftpd-2.2 ...
- IDEA加密算法(含所需jar包(commons-codec-1.11.jar ,bcprov-jdk15on-160.jar))
软件设计上机实验IDEA算法: import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.Se ...
- C++ 函数模板用法
泛型编程概念:不考虑具体数据类型的编程方式: 函数模板: 1.提供一种特殊的函数可用不同类型进行调用: 2.与普通函数很相似,区别是类型可被参数化: template <typename T&g ...
- JAVASE知识点总结(二)
第十三章:多态 一.instanceof 判断一个类是否是指定的类 真则返回true 假则返回false. 二.字段没有多态,只有方法有多态,字段前面是的什么类型,字段就调用谁的,在编译时就已经确 ...
- 10.Django基础八之cookie和session
一 会话跟踪 我们需要先了解一下什么是会话!可以把会话理解为客户端与服务器之间的一次会晤,在一次会晤中可能会包含多次请求和响应.例如你给10086打个电话,你就是客户端,而10086服务人员就是服务器 ...
- Centeos7部署Flask+Gunicorn+nginx
一.环境安装 pip3 install flask pip3 install gunicorn pip3 install nginx 二.模块介绍 1.Flask是一个使用 Python 编写的轻量级 ...