简单的互斥同步方式——synchronized关键字详解
java中要实现多线程的互斥同步访问,最简单的方式就是使用synchronized关键字。被其修饰的代码,相当于加了独占锁,线程只能互斥的访问,即同一时间只有一个线程能够访问这部分代码,其他线程只能在外等待。那么synchronized是如何做到这一点的?使用它还有哪些值得关注的细节?
2. synchronized的原理和实现细节
2.1 synchronized可以用在那些地方
- 静态方法,锁对象为当前类的class对象,不用显式指定
- 实例方法,锁对象为当前实例对象,不用显式指定
- 同步代码块,锁对象为括号中指定的对象,必须显式指定
被synchronized修饰的方法或者代码块,同一时刻只能有一个线程能够访问它。
2.2 synchronized是如何实现线程互斥访问的
虽然对于同步方法和同步代码块的底层细节略有不同,但都可以这么理解:对于被synchronized关键字修饰的代码区域,java虚拟机会在开始的位置插入monitorenter指令,而在结束和异常处插入monitorexit指令,每一个monitorenter指令必定有一个monitorexit指令与其配对。线程在执行到monitorenter指令时,将会尝试获取锁对象的monitor(监视器),该线程将进入同步方法或同步代码块中,同时monitor被锁定,防止被多个线程同时获取。获取monitor失败的线程将被阻塞在同步块或同步方法的入口处,整个过程如下图所示。

2.3 对象锁的monitor信息存储在哪
正如上面介绍的那样,任何对象都可以作为synchronized代码块的锁,而每个对象锁都有个monitor与之关联,线程通过获取该monitor的所有权来实现对被同步代码的互斥访问。这个monitor信息存储对象的对象头重。
2.4 monitor信息在对象头中的实现细节
对象锁的monitor信息是存储在该对象的对象头中。对象头的基本信息主要包括

由于锁信息是在Mark Word中,我们继续看看Mark Word到底包括哪些信息

内容比较多,不用强行记忆,只要知道大概有些东西就行了。
3. synchronized的内存语义
3.1 synchronized的可见性分析
由JMM的happens-before规则可知,对同步锁的释放happens-before于对同步锁的获取,因此在A线程释放锁之前对同步区域内共享变量作的修改,另一个线程B获取锁后将能够马上看到这种变化。它实现的原理和volatile类似
- A线程释放锁时,JMM会把该线程对应的工作内存中的共享变量刷新到主存中
- B线程获取锁后,JMM会把该线程对应的工作内存中的共享变量置为无效,同步代码块中的共享变量必须从主存中获取。
因此,如果共享变量只在同步代码块或者同步方法中被用到,那它是没必要用volatile修饰的!
3.2 synchronized禁止指令重排吗
那同步代码块会禁止指令重排吗?答案是不会。但只要同步块中的共享变量对其他线程不可见,那么我们就不必担心它引起的副作用。下面就是一个典型的例子
- 静态内部类的懒汉式实现
public class Singleton {
private Singleton() {
}
static class SingletonHolder {
public static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这是线程安全的实现方式,尽管INSTANCE变量没有用volatile修饰,但我们不用担心指令重排序带来的线程不安全问题。因为
INSTANCE = new Singleton();
是在类初始化的过程中执行的.JVM在Class文件被加载后,被线程使用前会执行类的初始化。这个过程会通过一个初始化锁进行同步(对于每一个类或接口,都有唯一的初始化锁),也就是说只有一个线程能获取该锁并执行这段代码,其他无法线程无法访问到INSTANCE变量,故重排序对其他线程不可见,整个过程是线程安全的。
4.锁的优化
为了减少频繁获得和释放锁的性能开销,JVM做了一系列优化。锁一共分为4种状态,级别从低到高依次为:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态。锁的状态会随着竞争的加剧逐渐升级,获得和释放锁的性能开销也逐渐加大,且锁只能升级而不能降级。为什么需要这么设置,它们各自又有什么特点,什么时候会出发锁的升级?
- 偏向锁: 当线程第一次访问同步块时,锁为偏向锁,该线程将一直持有偏向锁,直到有其他线程竞争该锁,该线程才释放锁,偏向锁也升级为轻量级锁。持有偏向锁的线程只在第一次获取时会进行CAS同步,接下来的访问和释放锁将不需要进行任何同步操作。
- 轻量级锁:轻量级锁在获取锁的时候会使用CAS操作将锁对象头的mark word替换到线程的栈帧中,释放锁则使用CAS替换回来。竞争线程不会阻塞,而是使用自旋的方式等待,同时轻量级锁将升级为重量级锁。
- 重量级锁:获取和释放锁更消耗性能,且竞争锁失败的线程将被阻塞。
下面是锁升级时的状态图

5. 总结
synchronized可以起到独占锁的作用,使多线程互斥的访问被其修饰的代码。虽然这种串行化会极大影响执行速度,但是能很好的保证线程安全,是开发中最经常使用的多线程技术之一。synchronized和volatile一样保证了多线程之间的可见性,又由于线程访问时的独占性对其他线程屏蔽了指令重排的细节,故而可以说是比volatile更重量级也更可靠的同步工具。
简单的互斥同步方式——synchronized关键字详解的更多相关文章
- “全栈2019”Java多线程第十六章:同步synchronized关键字详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- Java多线程(三)—— synchronized关键字详解
一.多线程的同步 1.为什么要引入同步机制 在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源.必须对这种潜在资源冲突进行预防. 解决方法:在线程使用一个资源时为其加锁即可. 访问资 ...
- Java synchronized 关键字详解
Java synchronized 关键字详解 前置技能点 进程和线程的概念 线程创建方式 线程的状态状态转换 线程安全的概念 synchronized 关键字的几种用法 修饰非静态成员方法 sync ...
- 从线程池到synchronized关键字详解
线程池 BlockingQueue synchronized volatile 前段时间看了一篇关于"一名3年工作经验的程序员应该具备的技能"文章,倍受打击.很多熟悉而又陌生的知识 ...
- synchronized关键字详解(一)
synchronized官方定义: 同步方法支持一种简单的策略防止线程干扰和内存一致性错误,如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过同步方法完成的(这一个synchroniz ...
- Java 多线程(六) synchronized关键字详解
多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同步用以解决多个线程同时访问时可能出现的问题. 同步机制可以使用synchronized关键字实现. 当synchroniz ...
- [java] java synchronized 关键字详解
Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码.当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一 ...
- synchronized关键字详解(二)
synchronized关键字的性质 1.可重入:同一线程的外层函数获得锁之后,内层函数可直接再次获得该锁,好处:避免死锁,提升封装性 证明可重入粒度:1.同一个方法是可重入的 2.可重入不要求是同一 ...
- java关键字(详解)
目录 1. 基本类型 1) boolean 布尔型 2) byte 字节型 3) char 字符型 4) double 双精度 5) float 浮点 6) int 整型 7) long 长整型 8) ...
随机推荐
- mysql 存储过程查询语句
可以用 命令"show PROCEDURE status"查看所有的存储过程或检索系统表"mysql.proc"来查询已有的存储过程.例如:用show PROC ...
- [转载] FFMPEG结构体分析:AVFrame
注:写了一系列的结构体的分析的文章,在这里列一个列表: FFMPEG结构体分析:AVFrameFFMPEG结构体分析:AVFormatContextFFMPEG结构体分析:AVCodecContext ...
- 如何制作CDLinux启动盘
用笔记本安装虚拟机并且尝试使用CDLinux系统进行无线密码破解的朋友们可能会遇到很多的问题,今天的经验就是总结了很多的失败然后整理出来的,希望能够对大家有所帮助.本次经验来和大家分享一下使用U盘制作 ...
- asp.net core mcroservices 架构之 分布式日志(三):集成kafka
一 kafka介绍 kafka是基于zookeeper的一个分布式流平台,既然是流,那么大家都能猜到它的存储结构基本上就是线性的了.硬盘大家都知道读写非常的慢,那是因为在随机情况下,线性下,硬盘的读写 ...
- Servlet中的Filter(过滤器)
Filter,过滤器,是处于客户端与服务器资源文件之间的一道过滤网,在访问资源文件之前,通过一系列的过滤器对请求进行修改.判断等,把不符合规则的请求在中途拦截或修改.也可以对响应进行过滤,拦截或修改 ...
- Notepad++如何取消打开最近的历史文件
1.设置 2.首选项 3.备份 4.取消勾选 "Remember current session for next launch" 5.重新启动即可. 出处:http://www. ...
- MySQL-5.7中InnoDB表数据文件存储位置
学习地址:https://www.cnblogs.com/tongxiaoda/p/7874535.html
- shell变量(字符串)间的连接
shell变量(字符串)间的连接 对于变量或者字符串的连接,shell提供了相当简单的做法,比string的连接还要直接. 直接放到一起或用双引号即可. 例如$a, $b,有 c=$a$b c=$a& ...
- 分布式锁之三:mysql实现-待整理
下面我们来看下开源dubbo推荐的业界成熟的zookeeper做为注册中心, zookeeper是hadoop的一个子项目是分布式系统的可靠协调者,他提供了配置维护,名字服务,分布式同步等服务.对于z ...
- linux输入子系统概念介绍
在此文章之前,我们讲解的都是简单的字符驱动,涉及的内容有字符驱动的框架.自动创建设备节点.linux中断.poll机制.异步通知.同步互斥.非阻塞.定时器去抖动. 上一节文章链接:http://blo ...