synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式。

  • 对于普通同步方法,锁是当前实例对象(this)。

  • 对于静态同步方法,锁是当前类的Class对象。

  • 对于同步方法块,锁是synchonized括号里配置的对象。

从JVM规范中可以看到synchonized在JVM里的实现原理,JVM基于进入和退出Monitor对象来实现方法同步和代码块同步;

monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处;

JVM要保证每个monitorenter必须有对应的monitorexit与之配对;

任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态;

线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁;

同步方法,依靠的 是方法修饰符上的 ACC_SYNCHRONIZED 实现;

synchronized用的锁是存在Java对象头里的,如果对象是数组类型,则虚拟机用3个字宽(Word)存储对象头,如果对象是非数组类型,则用2字宽存储对象头,在32位虚拟机中,1字宽等于4字节,即32bit。

Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”;

在Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级;

锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁,这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率

锁的表示

Java里的锁,主要都是对对象进行加锁,如普通的synchronized非静态方法,就是对当前执行方法的对象进行加锁。

那么怎么对对象进行加锁呢?对象的锁其实主要就是通过对象头的markOop(官方叫做MarkWord)进行表示的。markOop其实不是一个对象,只是一个字长的数据,在32为机器上,markOop为32个位,在64位上为64个位。

markOop中不同的位区域存储着不同的信息,但是需要注意的一点是,markOop每个位区域表示的信息不是一定的,在不同状态下,markWord中存着不同的信息。

下图很重要,是synchronized锁状态的对象头表示:

理解性记忆对象头可以从无锁->偏向->轻量->重量->GC标记 方式对比记忆,对象头里锁标志位一次是01->01->00->10->11

1 偏向锁

很多情况下,一个锁对象并不会发生被多个线程访问得情况,更多是被同一个线程进行访问,如果一个锁对象每次都被同一个线程访问,根本没有发生并发,但是每次都进行加锁,那岂不是非常耗费性能,所以偏向锁就被设计出来了;

偏向,也可以理解为偏心;

当锁对象第一次被某个线程访问时,它会在其对象头的markOop中记录该线程ID,那么下次该线程再次访问它时,就不需要进行加锁了;

当一个线程访问同步块并获取锁时,会在对象头栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁;

如果测试成功,表示线程已经获得了锁;如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程(线程ID);

但是这中间只要发生了其他线程访问该锁对象的情况,证明这个锁对象会发生并发,就不能对这个对象再使用偏向锁了,会进行锁的升级。

2 轻量级锁

当有竞争且竞争不强烈时,JVM就会由偏向锁膨胀为轻量级锁,考虑到线程的阻塞和唤醒需要CPU从用户态转为核心态(增加CPU负担),而这种转换对CPU来说是一件负担很重的操作,因此没有获取到锁的线程不会进入阻塞状态,而是通过自旋的方式一直尝试获取锁,处于一种忙等状态,所以这种处理竞争的方式比较浪费CPU,但是响应速度很快。

3 重量级锁

当竞争激烈时,不会立刻进入阻塞状态而是会自旋一段时间看是否能获取锁如果不能则进入阻塞状态。

三种锁的特点

优点 缺点 使用场景
偏向锁 加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级别的差距 如果线程间存在锁竞争,会带类额外的锁撤销的消耗 适用于只有一个线程访问同步块的场景
轻量级锁 竞争的线程不会阻塞,提高了程序的响应速度 如果始终得不到锁竞争的线程,使用自旋会消耗CPU 追求响应时间,同步块执行速度非常快
重量级锁 线程竞争几乎不使用自旋,不会消耗CPU 阻塞线程,响应时间缓慢 追求吞吐量,同步块执行速度较长

自旋锁

例子:死循环内sun.misc.Unsafe对象的CAS操作

来源:

https://blog.51cto.com/wenshengzhu/2062647

https://juejin.im/entry/5aad7773518825558252231a

https://juejin.im/post/5ae6dc04f265da0ba351d3ff

Java synchronized实现原理总结和偏量锁、轻量锁、重量锁、自旋锁的更多相关文章

  1. java synchronized 关键字原理

    Synchronized 关键字是解决并发问题常用解决方案,有以下三种使用方式: 同步普通方法,锁的是当前对象.同步静态方法,锁的是当前 Class 对象.同步块,锁的是 {} 中的对象. 实现原理: ...

  2. Java synchronized的原理解析

    开始 类有一个特性叫封装,如果一个类,所有的field都是private的,而且没有任何的method,那么这个类就像是四面围墙+天罗地网,没有门.看起来就是一个封闭的箱子,外面的进不来,里面的出不去 ...

  3. java 偏向锁,轻量锁,重量级锁

    synchronized的执行过程: 1. 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁 2. 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功 ...

  4. Java Synchronized的原理

    我们先通过反编译下面的代码来看看Synchronized是如何实现对代码块进行同步的: public class SynchronizedDemo{ public void method(){ syn ...

  5. java synchronized 的原理。

    synchronized的作用大概分为三种: 1.确保多线程互斥的访问多线程代码.2.保证变量的可见性.3.防止指令重排序. 那么synchronized 是如何实现这些功能的. public cla ...

  6. Java并发编程原理与实战十五:手动实现一个可重入锁

     package com.roocon.thread.ta1; public class Sequence { private MyLock lock = new MyLock(); private ...

  7. 【Java并发编程】24、Synchronized实现原理解析

    一.概述 我们知道在JDK1.5之前synchronized是一个重量级锁,相对于j.u.c.Lock,它会显得那么笨重,以至于我们认为它不是那么的高效而慢慢摒弃它. 不过,随着后续Java版本更新对 ...

  8. Java synchronized 关键字的实现原理

    数据同步需要依赖锁,那锁的同步又依赖谁?synchronized给出的答案是在软件层面依赖JVM,而Lock给出的方案是在硬件层面依赖特殊的CPU指令,大家可能会进一步追问:JVM底层又是如何实现sy ...

  9. synchronized底层实现原理&CAS操作&偏向锁、轻量级锁,重量级锁、自旋锁、自适应自旋锁、锁消除、锁粗化

    进入时:monitorenter 每个对象有一个监视器锁(monitor).当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:1 ...

随机推荐

  1. [LeetCode] 689. Maximum Sum of 3 Non-Overlapping Subarrays 三个非重叠子数组的最大和

    In a given array nums of positive integers, find three non-overlapping subarrays with maximum sum. E ...

  2. 多线程、线程池、线程创建、Thread

    转载自https://www.cnblogs.com/jmsjh/p/7762034.html 多线程 1.1 多线程介绍 学习多线程之前,我们先要了解几个关于多线程有关的概念. 进程:进程指正在运行 ...

  3. jira中使用eazyBI

    参考:https://docs.eazybi.com/eazybijira/set-up-and-administer/set-up-and-administer-for-jira-server/in ...

  4. [转帖]Linux中的find(-atime、-ctime、-mtime)指令分析

    Linux中的find(-atime.-ctime.-mtime)指令分析 https://www.cnblogs.com/zhangjinjin01/p/5505970.html https://w ...

  5. [转帖]中兴GoldenDB数据库开始了第一轮中信银行核心业务系统迁移落地

    中兴GoldenDB数据库开始了第一轮中信银行核心业务系统迁移落地 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https:// ...

  6. MySQL数据库去重 SQL解决

    MySQL数据库去重的方法 ​ 数据库最近有很多重复的数据,数据量还有点大,本想着用代码解决,后来发现用SQL就能解决,这里记录一下 看这条SQL DELETE consum_record FROM ...

  7. Python-11-生成器

    一.定义 可以理解为一种数据类型,这种数据类型自动实现了迭代器协议(其他数据类型需要调用__iter__方法),所以生成器就是一种迭代器. 二.生成器的两种形式 1. 生成器函数 使用yield代替r ...

  8. 【Centos】Centos7.5取消自动锁屏功能

    目录 00. 目录 01. 问题描述 02. 问题分析 03. 解决办法 04. 附录 00. 目录 @ 参考博客:[Centos]Centos7.5取消自动锁屏功能 01. 问题描述 Centos7 ...

  9. scope:provided影响子依赖

    一.问题 在上一篇<SpringBoot项目启动不走内嵌容器>中发现,provided会影响子依赖. 标记为scope:provided的jar在编译和运行时有作用,表明了运行时depen ...

  10. wait(),notify(),notifyAll()必须加锁的原因

    从语义方面解析为什么需要锁: 1.wait()方法会释放锁,如果没有先获得锁,那么如何释放? 从实际的作用: 为了预防饥饿线程的产生. 原因: // 线程A 的代码 while(!condition) ...