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知道这些就可以了的更多相关文章
- 面试必问的Synchronized知道这些就可以了
Synchronized关键字算是Java的元老级锁了,一开始它撑起了Java的同步任务,其用法简单粗暴容易上手.但是有些与它相关的知识点还是需要我们开发者去深入掌握的.比如,我们都知道通过Synch ...
- Java多线程4:synchronized锁机制
脏读 一个常见的概念.在多线程中,难免会出现在多个线程中对同一个对象的实例变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数据其实是被更改过 ...
- Java并发编程:synchronized
Java并发编程:synchronized 虽然多线程编程极大地提高了效率,但是也会带来一定的隐患.比如说两个线程同时往一个数据库表中插入不重复的数据,就可能会导致数据库中插入了相同的数据.今天我们就 ...
- Java多线程初学者指南(10):使用Synchronized关键字同步类方法
要想解决“脏数据”的问题,最简单的方法就是使用synchronized关键字来使run方法同步,代码如下: public synchronized void run() { ... } 从上面的代码可 ...
- synchronized关键字简介 多线程中篇(十一)
前面说过,Java对象都有与之关联的一个内部锁和监视器 内部锁是一种排它锁,能够保障原子性.可见性.有序性 从Java语言层面上说,内部锁使用synchronized关键字实现 synchronize ...
- 【转】Java并发编程:synchronized
一.什么时候会出现线程安全问题? 在单线程中不会出现线程安全问题,而在多线程编程中,有可能会出现同时访问同一个资源的情况,这种资源可以是各种类型的资源:一个变量.一个对象.一个文件.一个数据库表等,而 ...
- Java并发编程(四)synchronized
一.synchronized同步方法或者同步块 在了解synchronized关键字的使用方法之前,我们先来看一个概念:互斥锁,顾名思义:能到达到互斥访问目的的锁. 举个简单的例子:如果对临界资源加上 ...
- Java并发—synchronized关键字
synchronized关键字的作用是线程同步,而线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. synchronized用法 1. 在需要同步的方法的方法签名中加入synchro ...
- 使用Synchronized关键字同步类方法
要想解决“脏数据”的问题,最简单的方法就是使用synchronized关键字来使run方法同步,代码如下: public synchronized void run() { } 从上面的代码可以看出, ...
随机推荐
- file命令和readlink命令
6. 如何软链接设备文件 设备文件比较特殊,如果要创建设备文件的链接,需要用到mknod命令: 1 2 3 4 5 [root@centos7 etc]# ll /dev/sda brw-rw---- ...
- UVA - 11107 Life Forms (广义后缀自动机+后缀树/后缀数组+尺取)
题意:给你n个字符串,求出在超过一半的字符串中出现的所有子串中最长的子串,按字典序输出. 这道题算是我的一个黑历史了吧,以前我的做法是对这n个字符串建广义后缀自动机,然后在自动机上dfs,交上去AC了 ...
- 前端面试题-CSS Hack
一.CSS Hack的概念 由于不同厂商的流览器或某浏览器的不同版本(如IE,Firefox/Safari/Opera/Chrome等),对CSS的支持.解析不一样,导致在不同浏览器的环境中呈现出不一 ...
- SpringBoot项目中,WebSocket的使用(观察者设计模式)
1.什么是WebSocket(选择至菜鸟教程(点击跳转),观察者模式) WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议. WebSocket 使得客户端和 ...
- #5 DIV2 A POJ 3321 Apple Tree 摘苹果 构建线段树
Apple Tree Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 25232 Accepted: 7503 Descr ...
- C++ list用法总结
头文件 #include<list> 声明一个int型的list:list a: 1.list的构造函数 list<int>a{1,2,3} list<int>a( ...
- oracle 获取时间
1.获取当前时间的前24小时的各小时时间段 select to_char(to_date(to_char(sysdate ) ,'yyyy-mm-dd hh24') || ':00:00','yyyy ...
- Unit Test in SpringBoot
此处的Unit Test in SpringBoot 包括: SpringApplication Test Service Test ControllerTest 测试项目结构如下: 代码如下: PO ...
- 完美解决前端跨域之 easyXDM 的使用和解析
前端跨域问题在大型网站中是比较常见的问题.本文详细介绍了利用 easyXDM 解决前端跨域的原理细节和使用细节,具体使用时可以在文中代码实例的基础上扩展完成. 0.背景 因个别网络运营商存在 HTTP ...
- jsp里面include的静态导入和动态导入的区别
静态导入就是将被导入页面完全融入到导入的页面中:而动态导入只是在servlet里面插入了include方法,导入的这是被导入页面的body标签里面的内容 1.什么是静态导入? 静态导入指的是,将一个外 ...