Java synchronized的使用与原理
需要明确的几个问题:
- synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果 再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
- 无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。
- 每个对象只有一个锁(lock)与之相关联。
- 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
synchronized关键字的作用域有二种:
- 某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线 程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的 synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
- 某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
1、使用在方法上synchronized aMethod(){...}
使用相同的 object

public class synchTest {
private String a= "";
private List<String> b= new ArrayList<>();
// 方法一
public void job() {
System.out.println("job .....");
synchronized (b){
System.out.println("job 使用锁中 ....");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
}
System.out.println("job end.....");
}
// 方法二
public synchronized void job2(){
System.out.println("job2 .....");
synchronized (b){
System.out.println("job22 使用锁中 ...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
}
System.out.println("job2 end.....");
}
public static void main(String[] args) {
final synchTest rs = new synchTest();
new Thread() {
public void run() {
rs.job();
}
}.start();
new Thread() {
public void run() {
rs.job2();
}
}.start();
}
}
结果:
job .....
job 使用锁中 ....
job2 .....
job end.....
job22 使用锁中 ...
job2 end.....
使用不同的object

public class synchTest {
private String a= "";
private List<String> b= new ArrayList<>();
// 方法一
public void job() {
System.out.println("job .....");
synchronized (a){
System.out.println("job 使用锁中 ....");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
}
System.out.println("job end.....");
}
// 方法二
public synchronized void job2(){
System.out.println("job2 .....");
synchronized (b){
System.out.println("job22 使用锁中 ...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
}
System.out.println("job2 end.....");
}
public static void main(String[] args) {
final synchTest rs = new synchTest();
new Thread() {
public void run() {
rs.job();
}
}.start();
new Thread() {
public void run() {
rs.job2();
}
}.start();
}
}
结果:
job .....
job 使用锁中 ....
job2 .....
job22 使用锁中 ...
job end.....
job2 end.....
使用this关键词

public class synchTest {
private String a= "";
private List<String> b= new ArrayList<>();
// 方法一
public void job() {
System.out.println("job .....");
synchronized (this){
System.out.println("job 使用锁中 ....");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
}
System.out.println("job end.....");
}
// 方法二
public synchronized void job2(){
System.out.println("job2 .....");
synchronized (b){
System.out.println("job22 使用锁中 ...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
}
System.out.println("job2 end.....");
}
public static void main(String[] args) {
final synchTest rs = new synchTest();
new Thread() {
public void run() {
rs.job();
}
}.start();
new Thread() {
public void run() {
rs.job2();
}
}.start();
}
}
结果:
job .....
job 使用锁中 ....
job end.....
job2 .....
job22 使用锁中 ...
job2 end.....
结论:
- synchronized(Object) object相同的情况下,修饰的内容会同步,等上一个执行完才能执行下一个方法的内容
- synchronized(Object) object不相同的情况下,修饰内容不会同步,两个方法可以一起执行
- this这个比较特殊,如果先执行修饰this这个方法的内容,会同步,否则 不会同步(可以测试下) 【慎用 this同步块,会锁对象】
2、使用在方法内部 synchronized(Oject){...}

public class synchTest {
// 方法一
public synchronized void job() {
System.out.println("job .....");
System.out.println("job 使用锁中 ....");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println("job end.....");
}
// 方法二
public synchronized void job2(){
System.out.println("job2 .....");
System.out.println("job22 使用锁中 ...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println("job2 end.....");
}
public static void main(String[] args) {
final synchTest rs = new synchTest();
new Thread() {
public void run() {
rs.job();
}
}.start();
new Thread() {
public void run() {
rs.job2();
}
}.start();
}
}
结果:
job .....
job 使用锁中 ....
job end.....
job2 .....
job22 使用锁中 ...
job2 end.....
结论:
- 对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法,在对象内容所有的synchronized 的方法都会同步,必须等上一个方法执行完才能执行下一个方法
2、使用在方法内部 synchronized(Oject){...}、synchronized aMethod(){...}混用

public class synchTest {
public String a = "";
// 方法一
public void job() {
System.out.println("job .....");
synchronized (a){
System.out.println("job 使用锁中 ....");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
}
System.out.println("job end.....");
}
// 方法二
public synchronized void job2(){
System.out.println("job2 .....");
System.out.println("job22 使用锁中 ...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println("job2 end.....");
}
public static void main(String[] args) {
final synchTest rs = new synchTest();
new Thread() {
public void run() {
rs.job();
}
}.start();
new Thread() {
public void run() {
rs.job2();
}
}.start();
}
}
结果:
job .....
job 使用锁中 ....
job2 .....
job22 使用锁中 ...
job end.....
job2 end.....
结论:
对象实例内 synchronized aMethod(){} 与synchronized(Object) 不会相互同步
二、原理
Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。
synchronized用的锁是存在Java对象头里的。
JVM基于进入和退出Monitor对象来实现方法同步和代码块同步。代码块同步是使用monitorenter和monitorexit指令实现的,monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。
根据虚拟机规范的要求,在执行monitorenter指令时,首先要去尝试获取对象的锁,如果这个对象没被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器加1;
相应地,在执行monitorexit指令时会将锁计数器减1,当计数器被减到0时,锁就释放了。
如果获取对象锁失败了,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。
注意两点:
1、synchronized同步快对同一条线程来说是可重入的,不会出现自己把自己锁死的问题;
2、同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入。

(图摘自:https://blog.csdn.net/javazejian/article/details/72828483)
Synchronized在jvm字节码上的体现
我们以之前的例子为例,使用javac编译代码,然后使用javap进行反编译。

反编译后部分片段如下图:

对于使用synchronized修饰的方法,反编译后字节码中会有ACC_SYNCHRONIZED关键字。

而synchronized修饰的代码块中,在代码块的前后会有monitorenter、monitorexit关键字,此处的字节码中有两个monitorexit是因为我们有try-catch语句块,有两个出口。
Synchronized与等待唤醒
等待唤醒是指调用对象的wait、notify、notifyAll方法。调用这三个方法时,对象必须被synchronized修饰,因为这三个方法在执行时,必须获得当前对象的监视器monitor对象。
另外,与sleep方法不同的是wait方法调用完成后,线程将被暂停,但wait方法将会释放当前持有的监视器锁(monitor),直到有线程调用notify/notifyAll方法后方能继续执行。而sleep方法只让线程休眠并不释放锁。notify/notifyAll方法调用后,并不会马上释放监视器锁,而是在相应的synchronized代码块或synchronized方法执行结束后才自动释放锁。
自旋锁与自适应自旋锁
- 引入自旋锁的原因:互斥同步对性能最大的影响是阻塞的实现,因为挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性能带来很大的压力。同时虚拟机的开发团队也注意到在许多应用上面,共享数据的锁定状态只会持续很短一段时间,为了这一段很短的时间频繁地阻塞和唤醒线程是非常不值得的。
- 自旋锁:让该线程执行一段无意义的忙循环(自旋)等待一段时间,不会被立即挂起(自旋不放弃处理器额执行时间),看持有锁的线程是否会很快释放锁。自旋锁在JDK 1.4.2中引入,默认关闭,但是可以使用-XX:+UseSpinning开开启;在JDK1.6中默认开启。
- 自旋锁的缺点:自旋等待不能替代阻塞,虽然它可以避免线程切换带来的开销,但是它占用了处理器的时间。如果持有锁的线程很快就释放了锁,那么自旋的效率就非常好;反之,自旋的线程就会白白消耗掉处理器的资源,它不会做任何有意义的工作,这样反而会带来性能上的浪费。所以说,自旋等待的时间(自旋的次数)必须要有一个限度,例如让其循环10次,如果自旋超过了定义的时间仍然没有获取到锁,则应该被挂起(进入阻塞状态)。通过参数-XX:PreBlockSpin可以调整自旋次数,默认的自旋次数为10。
- 自适应的自旋锁:JDK1.6引入自适应的自旋锁,自适应就意味着自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定:如果在同一个锁的对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后要获取这个锁时将可能省略掉自旋过程,以避免浪费处理器资源。简单来说,就是线程如果自旋成功了,则下次自旋的次数会更多,如果自旋失败了,则自旋的次数就会减少。
- 自旋锁使用场景:从轻量级锁获取的流程中我们知道,当线程在获取轻量级锁的过程中执行CAS操作失败时,是要通过自旋来获取重量级锁的。(见前面“轻量级锁”)
总结
- synchronized特点:保证内存可见性、操作原子性
- synchronized影响性能的原因:
- 1、加锁解锁操作需要额外操作;
- 2、互斥同步对性能最大的影响是阻塞的实现,因为阻塞涉及到的挂起线程和恢复线程的操作都需要转入内核态中完成(用户态与内核态的切换的性能代价是比较大的)
- synchronized锁:对象头中的Mark Word根据锁标志位的不同而被复用
- 偏向锁:在只有一个线程执行同步块时提高性能。Mark Word存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单比较ThreadID。特点:只有等到线程竞争出现才释放偏向锁,持有偏向锁的线程不会主动释放偏向锁。之后的线程竞争偏向锁,会先检查持有偏向锁的线程是否存活,如果不存货,则对象变为无锁状态,重新偏向;如果仍存活,则偏向锁升级为轻量级锁,此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁
- 轻量级锁:在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,尝试拷贝锁对象目前的Mark Word到栈帧的Lock Record,若拷贝成功:虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向对象的Mark Word。若拷贝失败:若当前只有一个等待线程,则可通过自旋稍微等待一下,可能持有轻量级锁的线程很快就会释放锁。 但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁
- 重量级锁:指向互斥量(mutex),底层通过操作系统的mutex lock实现。等待锁的线程会被阻塞,由于Linux下Java线程与操作系统内核态线程一一映射,所以涉及到用户态和内核态的切换、操作系统内核态中的线程的阻塞和恢复。
Java synchronized的使用与原理的更多相关文章
- Java synchronized 关键字的实现原理
数据同步需要依赖锁,那锁的同步又依赖谁?synchronized给出的答案是在软件层面依赖JVM,而Lock给出的方案是在硬件层面依赖特殊的CPU指令,大家可能会进一步追问:JVM底层又是如何实现sy ...
- java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析
java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...
- Java线程:概念与原理
Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...
- Java并发之底层实现原理学习笔记
本篇博文将介绍java并发底层的实现原理,我们知道java实现的并发操作最后肯定是由我们的CPU完成的,中间经历了将java源码编译成.class文件,然后进行加载,然后虚拟机执行引擎进行执行,解释为 ...
- Java 连接池的工作原理(转)
原文:Java 连接池的工作原理 什么是连接? 连接,是我们的编程语言与数据库交互的一种方式.我们经常会听到这么一句话“数据库连接很昂贵“. 有人接受这种说法,却不知道它的真正含义.因此,下面我将解释 ...
- java并发包&线程池原理分析&锁的深度化
java并发包&线程池原理分析&锁的深度化 并发包 同步容器类 Vector与ArrayList区别 1.ArrayList是最常用的List实现类,内部是通过数组实现的, ...
- java synchronized实现可见性对比volatile
问题: 大家可以先看看这个问题,看看这个是否有问题呢? 那里有问题呢? public class ThreadSafeCache { int result; public int getResult( ...
- Java虚拟机类装载的原理及实现(转)
Java虚拟机类装载的原理及实现(转) 一.引言 Java虚拟机(JVM)的类装载就是指将包含在类文件中的字节码装载到JVM中, 并使其成为JVM一部分的过程.JVM的类动态装载技术能够在运行时刻动态 ...
- Java volatile 关键字底层实现原理解析
本文转载自Java volatile 关键字底层实现原理解析 导语 在Java多线程并发编程中,volatile关键词扮演着重要角色,它是轻量级的synchronized,在多处理器开发中保证了共享变 ...
- Java 多线程与并发【原理第二部分笔记】
Java 多线程与并发[原理第二部分笔记] 什么是Java内存模型中的happens-before Java内存模型,即JMM,本身是一种抽象的概念,并不是真实存在的,他描述的是一组规则或者说是一种规 ...
随机推荐
- vue基础系列文章12---创建脚手架
一.交互式命令行创建 1.运行 vue create myvue 选择默认创建模式,会在指定的文件夹下创建文件 2.进入到myvue文件夹,运行:npm run serve 3.访问本地的地址就可以 ...
- 从零开始配置vim(28)——代码的编译、运行与调试
在前面几个章节,我们逐渐为 Vim 配置了语法高亮.代码的跳转和自动补全功能.现在的 Vim 已经可以作为代码编辑器来使用了.但是想将它作为日常发开的主力编辑器来用还需要很长一段路要走,其中一个就是要 ...
- GO中的GC
go中的垃圾回收 前言 垃圾回收 go中的垃圾回收方式 三色标记法 根对象 STW 屏障技术 插入屏障 删除屏障 混合写屏障 GO中GC的流程 GC的触发时机 如果内存分配速度超过了标记清除的速度怎么 ...
- 书写自动智慧文本分类器的开发与应用:支持多分类、多标签分类、多层级分类和Kmeans聚类
书写自动智慧文本分类器的开发与应用:支持多分类.多标签分类.多层级分类和Kmeans聚类 文本分类器,提供多种文本分类和聚类算法,支持句子和文档级的文本分类任务,支持二分类.多分类.多标签分类.多层级 ...
- 基于无监督训练SimCSE+In-batch Negatives策略有监督训练的语义索引召回
基于无监督训练SimCSE+In-batch Negatives策略有监督训练的语义索引召回 语义索引(可通俗理解为向量索引)技术是搜索引擎.推荐系统.广告系统在召回阶段的核心技术之一.语义索引模型的 ...
- SecureCRT使用sftp上传较大文件意外暂停
今天在一台跳转机使用SecureCRT的sftp上传较大文件时,发现中途会时常停止传输,给出提示如下: 如果此时选择Accept Once,会继续传输,但是过一段时间后,还是会停止并给出相同提示,如果 ...
- Net5 WorkService 继承 Quarzt 以及 Net5处理文件上传
Net5 版本以Core为底层非framework框架的windowservice 服务. 在VS里叫WorkService 可以以CMD方式运行也可以以Windowservice方式运行,部署简单. ...
- 扩展说明: 指令微调 Llama 2
这篇博客是一篇来自 Meta AI,关于指令微调 Llama 2 的扩展说明.旨在聚焦构建指令数据集,有了它,我们则可以使用自己的指令来微调 Llama 2 基础模型. 目标是构建一个能够基于输入内容 ...
- JS leetcode 拥有最多糖果的孩子 题解分析,六一快乐。
壹 ❀ 引 今天是六一儿童节,leetcode的每日一题也特别可爱,那么今天我们来解决一道与糖果有关的问题,题目来源1431. 拥有最多糖果的孩子,题目描述如下: 给你一个数组 candies 和一个 ...
- JS leetcode 两数之和 II - 输入有序数组 题解分析
壹 ❀ 引 我在JS leetcode 两数之和 解答思路分析一文中首次解决两数之和等于目标值的问题,那么今天遇到的是两数之和的升级版,题目为leetcode167. 两数之和 II - 输入有序数组 ...