Synchronzied原理精讲

  • 原理
    • 底层原理
    • monitor监视器锁
    • 什么是monitor?
    • synchronized 加锁方式

原理

synchronized 内置锁是一种对象所,作用力度是对象,可以用来实现对临街资源的同步互斥访问,可重入;
加锁方式:
- 同步实例方法,锁得是当前实例对象;
- 同步类方法,所得是当前类对象;
- 同步代码块,所得是括号里的对象;

底层原理

synchronized是基于JVM内置锁实现,通过内部对象Monitor(监视器锁)实现,基于进入与退出Monitor对象实现方法与代码 块同步,监视器锁的实现依赖底层操作系统的Mutex lock(互斥锁)实现,它是一个重量级锁性能较低。当然,JVM内置锁在1.5 之后版本做了重大的优化,如锁粗化(Lock Coarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight Locking)、
偏向锁(Biased Locking)、适应性自旋(Adaptive Spinning)等技术来减少锁操作的开销,,内置锁的并发性能已经基本与 Lock持平。

synchronized关键字被编译成字节码后会被翻译成monitorenter 和 monitorexit 两条指令分别在同步块逻辑代码的起始位置 与结束位置。
每个同步对象都有一个自己的Monitor(监视器锁),加锁过程如下图所示:

monitor监视器锁

任何一个对象都有一个Monitor与之关联,当且一个Monitor被持有后,它将处于锁定状态。Synchronized在JVM里的实现都是 基于进入和退出Monitor对象来实现方法同步和代码块同步。

  • monitorenter:每个对象都是一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行 monitorenter指令时尝试获取monitor的所有权,过程如下:
  1. 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor 的所有者;
  2. 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;
  3. 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权;
  • monitorexit:执行monitorexit的线程必须是objectref所对应的monitor的所有者。指令执行时,monitor的进入数减 1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去 获取这个 monitor 的所有权。 monitorexit,指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异步退出释放锁;
    通过上面两段描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来 完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则 会抛出java.lang.IllegalMonitorStateException的异常的原因。

什么是monitor?

可以把它理解为 一个同步工具,也可以描述为 一种同步机制,它通常被 描述为一个对象。与一切皆对象一样,所有的Java对象 是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把 看不见的锁,它叫做内部锁或者Monitor锁。markWord锁标识位为10,其中指针指向的是monitor对象的其实地址。
在Java虚拟机(HotSpot)中,Monitor是由ObjectMonitor实现的,其主要数据结构如下(位于 HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的):

ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成 ObjectWaiter对象 ),_owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时:

  1. 首先会进入 _EntryList 集合,当线程获取到对象的monitor后,进入 _Owner区域并把monitor中的owner变量设置为当前线程,同时monitor中的计数器count加1;
  2. 若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSet 集合中等待被唤醒;
  3. 唤醒调用notify方法,可以随机唤醒_WaitSet队列里的一个线程,notifyall唤醒 _WaitSet队列里的所有线程;
  4. 若当前线程执行完毕,也将释放monitor(锁)并复位count的值,以便其他线程进入获取monitor(锁); 同时,Monitor对象存在于每个Java对象的对象头Mark Word中(存储的指针的指向),Synchronized锁便是通过这种方式 获取锁的,也是为什么Java中任意对象可以作为锁的原因,同时notify/notifyAll/wait等方法会使用到Monitor锁对象,所以必须
    在同步代码块中使用。监视器Monitor有两种同步方式:互斥与协作。多线程环境下线程之间如果需要共享数据,需要解决互斥访问 数据的问题,监视器可以确保监视器上的数据在同一时刻只会有一个线程在访问。

synchronized 加锁方式

我们知道class类对象是对象,实例对象也是对象,任何对象都可以加锁。

  1. Synchronized修饰非静态方法,实际上是对调用该方法的对象加锁,俗称“对象锁”。
    Java中每个对象都有一个锁,并且是唯一的。假设分配的一个对象空间,里面有多个方法,相当于空间里面有多个小房间,如果我们把所有的小房间都加锁,因为这个对象只有一把钥匙,因此同一时间只能有一个人打开一个小房间,然后用完了还回去,再由JVM 去分配下一个获得钥匙的人。
    情况1:同一个对象在两个线程中分别访问该对象的两个同步方法
    结果:会产生互斥。
    解释:因为锁针对的是对象,当对象调用一个synchronized方法时,其他同步方法需要等待其执行结束并释放锁后才能执行。
    情况2:不同对象在两个线程中调用同一个同步方法
    结果:不会产生互斥。
    解释:因为是两个对象,锁针对的是对象,并不是方法,所以可以并发执行,不会互斥。形象的来说就是因为我们每个线程在调用方法的时候都是new 一个对象,那么就会出现两个空间,两把钥匙。

    2.Synchronized修饰静态方法,实际上是对该类对象加锁,俗称“类锁”。
    情况1:用类直接在两个线程中调用两个不同的同步方法
    结果:会产生互斥。
    解释:因为对静态对象加锁实际上对类(.class)加锁,类对象只有一个,可以理解为任何时候都只有一个空间,里面有N个房间,一把锁,因此房间(同步方法)之间一定是互斥的。
    注:上述情况和用单例模式声明一个对象来调用非静态方法的情况是一样的,因为永远就只有这一个对象。所以访问同步方法之间一定是互斥的。
    情况2:用一个类的静态对象在两个线程中调用静态方法或非静态方法
    结果:会产生互斥。
    解释:因为是一个对象调用,同上。
    情况3:一个对象在两个线程中分别调用一个静态同步方法和一个非静态同步方法
    结果:不会产生互斥。
    解释:因为虽然是一个对象调用,但是两个方法的锁类型不同,调用的静态方法实际上是类对象在调用,即这两个方法产生的并不是同一个对象锁,因此不会互斥,会并发执行。

测试代码:

public class SynchronizedTest {//    private SynchronizedTest(){}private static SynchronizedTest st;           //懒汉式单例模式,线程不安全,需要加synchronized同步public static SynchronizedTest getInstance(){if(st == null){st = new SynchronizedTest();}return st;}public static SynchronizedTest staticIn = new SynchronizedTest();   //静态对象public synchronized void method1(){                                      //非静态方法1for(int i = 0;i < 5;i++){System.out.println("method1 is running!");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}public synchronized  void method2(){                                   //非静态方法2for( int i = 0; i < 5 ; i++){System.out.println("method2 is running!");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}public synchronized static void staticMethod1(){                     //静态方法1for( int i = 0; i < 10 ; i++){System.out.println("static method1 is running!");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}public synchronized static void staticMethod2(){                      //静态方法2for( int i = 0; i < 10 ; i++){System.out.println("static method2 is running!");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
}
public class Thread1 implements Runnable {@Overridepublic void run() {//同一个对象在两个线程中分别访问该对象的两个同步方法,会互斥,因为锁针对的是对象//当对象调用一个synchronized方法时,其他同步方法需要等待其执行结束并释放锁后才能执行
//      SynchronizedTest s = SynchronizedTest.getInstance();
//      s.method1();
//        不同对象在两个线程中调用同一个同步方法,不会互斥,因为这是两个对象
//        SynchronizedTest s1 = new SynchronizedTest();
//        s1.method1();//用一个类的静态对象在两个线程中调用静态方法或非静态方法,互斥,因为是一个对象调用的锁
//       SynchronizedTest.staticIn.method1();
//        SynchronizedTest.staticMethod1();// 一个对象在两个线程中分别调用一个静态同步方法和一个非静态同步方法,不互斥,锁类型不一样,不是同一个对象所SynchronizedTest.staticMethod1();}
}
public class Thread2 implements Runnable{@Overridepublic void run() {// TODO Auto-generated method stub
//      SynchronizedTest s = SynchronizedTest.getInstance();
//      s.method2();//      SynchronizedTest s2 = new SynchronizedTest();
//      s2.method2();
//      SynchronizedTest.staticMethod1();SynchronizedTest.staticIn.method2();
//      SynchronizedTest.staticMethod2();
//      SynchronizedTest.staticIn.method2();//     SynchronizedTest.staticIn.staticMethod1();}
}
public class ThreadMain {public static void main(String[] args) {Thread t1 = new Thread(new Thread1());t1.start();Thread t2 = new Thread(new Thread2());t2.start();}}

总结:
1.对象锁钥匙只能有一把才能互斥,才能保证共享变量的唯一性
2.在静态方法上的锁,和 实例方法上的锁,默认不是同样的,如果同步需要制定两把锁一样。
3.关于同一个类的方法上的锁,来自于调用该方法的对象,如果调用该方法的对象是相同的,那么锁必然相同,否则就不相同。比如 new A().x() 和 new A().x(),对象不同,锁不同,如果A的单利的,就能互斥。
4.静态方法加锁,能和所有其他静态方法加锁的 进行互斥
5.静态方法加锁,和xx.class 锁效果一样,直接属于类的

了解Synchronzied原理一篇就够了!相关推荐

  1. IM扫码登录技术专题(三):通俗易懂,IM扫码登录功能详细原理一篇就够

    本文引用了作者"大古同学"的"二维码扫码登录是什么原理"一文的主要内容,为了更好的理解和阅读,即时通讯网收录时有修订和改动,感谢原作者的分享. 1.引言 自从微 ...

  2. 学习OpenStack原理一篇就够了!!!

    文章目录 一:OpenStack的起源 二:云计算 2.1:云计算概念 2.2:云计算 三:OpenStack优点 四:OpenStack架构 4.1:架构图 4.2:各组件服务作用 五:OpenSt ...

  3. Handler原理剖析,看这篇就够了

    Handler原理剖析,看这篇就够了 本篇文章将会对Handler进行深层次的剖析,结合关系剖析图.代码走向剖析图以及10个常见问题,希望看完文章的同学都能有所收获,加深对Handler的了解! 一. ...

  4. React入门看这篇就够了

    2019独角兽企业重金招聘Python工程师标准>>> 摘要: 很多值得了解的细节. 原文:React入门看这篇就够了 作者:Random Fundebug经授权转载,版权归原作者所 ...

  5. [mmu/cache]-ARM cache的学习笔记-一篇就够了

    ★★★ 个人博客导读首页-点击此处 ★★★ . 说明: 在默认情况下,本文讲述的都是ARMV8-aarch64架构,linux kernel 64位 . 相关文章 1.ARM MMU的学习笔记-一篇就 ...

  6. serviceloader java_【java编程】ServiceLoader使用看这一篇就够了

    转载:https://www.jianshu.com/p/7601ba434ff4 想必大家多多少少听过spi,具体的解释我就不多说了.但是它具体是怎么实现的呢?它的原理是什么呢?下面我就围绕这两个问 ...

  7. 史上最全!用Pandas读取CSV,看这篇就够了

    导读:pandas.read_csv接口用于读取CSV格式的数据文件,由于CSV文件使用非常频繁,功能强大,参数众多,因此在这里专门做详细介绍. 作者:李庆辉 来源:大数据DT(ID:hzdashuj ...

  8. Spring Cloud入门,看这篇就够了!

    点击▲关注 "中生代技术"   给公众号标星置顶 更多精彩 第一时间直达 概述 首先我给大家看一张图,如果大家对这张图有些地方不太理解的话,我希望你们看完我这篇文章会恍然大悟. 什 ...

  9. MySQL 异常有这一篇就够了!

    摘要:在本文中,总结了开发过程中最为常见的几种 MySQL 抛出的异常以及如何解决,包括高版本驱动的问题.时区配置问题.SSL 连接问题等,是一篇经验总结贴. 前言 在本文中,总结了开发过程中最为常见 ...

最新文章

  1. 蚌埠智慧城市建设跻身全国22强 让数据替群众“跑腿”
  2. 应用基础计算机一级的题目,计算机应用基础一级模拟题
  3. 浅谈Android系统进程间通信(IPC)机制Binder中的Server和Client获得Service Manager接口之路
  4. 第三次学JAVA再学不好就吃翔(part61)--基本数据类型包装类
  5. 巨控 自建服务器,GRM云服务器的Web数据接口.PDF
  6. 修复bug的12个关键步骤
  7. Structure from motion 问题
  8. IDEA——Git 的设置与使用
  9. 关于Git和Github
  10. kafka专题:kafka的总控制器Controller、消费者重分配策略等核心设计原理详解
  11. Power Pivot表属性无法切换回表预览模式的问题
  12. Vivado时序报告名词解释
  13. Java和C语言学习那个比较好?
  14. JDBC工具类DataSourceUtils,dao接口代码示例;
  15. Day4 单用户模式、救援模式、克隆虚拟机、linux机器相互登录
  16. 程序猿的道路~~(How to be a programmer?)
  17. 聪明好学的王强用计算机设计了,五年级语文下册期中试卷1.doc
  18. 【算法设计与分析】01 算法涉及的研究内容概述
  19. 【下载https协议需要的cer证书】
  20. 计算机黑屏的原因及修复,导致笔记本电脑开机黑屏的原因以及对应的修复方法...

热门文章

  1. 密立根油滴实验数据处理(基于Python)
  2. 搭建基于瓦片的离线地图应用
  3. 试图执行的操作不受支持解决办法
  4. TMS320C6747的emifa访问异步外部存储
  5. 《抽样技术》第5章 不等概抽样
  6. MATLAB用GARCH-EVT-Copula极值理论模型VaR预测分析股票投资组合
  7. Excel数据分析从入门到精通(十五)数据透视表之动态仪表盘
  8. intellij idea 设置显示空格
  9. 我的产品不需要盈利:战略单品的存在意义
  10. Android automotive车载开发(1)-----Automotive audio