当前位置: 首页 > news >正文

Synchronized 的底层原理——Java全栈知识(40)

Synchronized 的底层原理

1、Synchronized 如何使用?

我们都知道 Synchronized 关键字会给代码上互斥锁,但是上锁的位置分为两种,分别是对象锁和类锁

1.1、对象锁

1、Synchronized 默认锁的对象是 this,也可以手动指定锁对象。
案例一(锁 this 对象):

public class SynchronizedObjectLock implements Runnable {  static SynchronizedObjectLock instance = new SynchronizedObjectLock();  @Override  public void run() {  // 同步代码块形式——锁为this,两个线程使用的锁是一样的,线程1必须要等到线程0释放了该锁后,才能执行  synchronized (this) {  System.out.println("我是线程" + Thread.currentThread().getName());  try {  Thread.sleep(3000);  } catch (InterruptedException e) {  e.printStackTrace();  }  System.out.println(Thread.currentThread().getName() + "结束");  }  }  public static void main(String[] args) {  Thread t1 = new Thread(instance);  Thread t2 = new Thread(instance);  t1.start();  t2.start();  }  
}

运行结果是:


我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束

[!note]

1、我们可以看到 t 1 运行时锁住了 instance 对象

2、t 2 开始运行获取当前对象的锁

3、阻塞等待到 t 1 释放锁,t 2 获取锁执行。

2、Synchronized 指定锁对象

案例二(指定锁对象):

public class SynchronizedObjectLock implements Runnable {  static SynchronizedObjectLock instance = new SynchronizedObjectLock();  // 创建2把锁  Object block1 = new Object();  Object block2 = new Object();  @Override  public void run() {  // 这个代码块使用的是第一把锁,当他释放后,后面的代码块由于使用的是第二把锁,因此可以马上执行  synchronized (block1) {  System.out.println("block1锁,我是线程" + Thread.currentThread().getName());  try {  Thread.sleep(3000);  } catch (InterruptedException e) {  e.printStackTrace();  }  System.out.println("block1锁,"+Thread.currentThread().getName() + "结束");  }  synchronized (block2) {  System.out.println("block2锁,我是线程" + Thread.currentThread().getName());  try {  Thread.sleep(3000);  } catch (InterruptedException e) {  e.printStackTrace();  }  System.out.println("block2锁,"+Thread.currentThread().getName() + "结束");  }  }  public static void main(String[] args) {  Thread t1 = new Thread(instance);  Thread t2 = new Thread(instance);  t1.start();  t2.start();  }  
}

block1锁,我是线程Thread-0  
block1锁,Thread-0结束  
block2锁,我是线程Thread-0  // 可以看到当第一个线程在执行完第一段同步代码块之后,第二个同步代码块可以马上得到执行,因为他们使用的锁不是同一把  
block1锁,我是线程Thread-1  
block2锁,Thread-0结束  
block1锁,Thread-1结束  
block2锁,我是线程Thread-1  
block2锁,Thread-1结束

[!note]

1、t 1 线程获取到 block 1,t 2 线程阻塞等待

2、t 1 线程释放 block 1 获取到 block 2,t 2 获取到 block1

3、t 1 线程释放 block 2 ,t 2 线程获取到 block 2 执行

3、方法锁:synchronized 修饰普通方法,锁对象默认为 this

案例三:

public class SynchronizedObjectLock implements Runnable {  static SynchronizedObjectLock instance = new SynchronizedObjectLock();  @Override  public void run() {  method();  }  public synchronized void method() {  System.out.println("我是线程" + Thread.currentThread().getName());  try {  Thread.sleep(3000);  } catch (InterruptedException e) {  e.printStackTrace();  }  System.out.println(Thread.currentThread().getName() + "结束");  }  public static void main(String[] args) {  Thread t1 = new Thread(instance);  Thread t2 = new Thread(instance);  t1.start();  t2.start();  }  
}

运行结果是:


我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束

1.2、类锁

指 synchronize 修饰静态的方法或指定锁对象为 Class 对象

1、 synchronize 修饰静态方法

示例一(修饰普通方法):

public class SynchronizedObjectLock implements Runnable {static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();@Overridepublic void run() {method();}// synchronized用在普通方法上,默认的锁就是this,当前实例public synchronized void method() {System.out.println("我是线程" + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "结束");}public static void main(String[] args) {// t1和t2对应的this是两个不同的实例,所以代码不会串行Thread t1 = new Thread(instance1);Thread t2 = new Thread(instance2);t1.start();t2.start();}
}

输出结果:

我是线程Thread-0
我是线程Thread-1
Thread-1结束
Thread-0结束
  • 示例2(synchronized 修饰静态方法)
public class SynchronizedObjectLock implements Runnable {static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();@Overridepublic void run() {method();}// synchronized用在静态方法上,默认的锁就是当前所在的Class类,所以无论是哪个线程访问它,需要的锁都只有一把public static synchronized void method() {System.out.println("我是线程" + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "结束");}public static void main(String[] args) {Thread t1 = new Thread(instance1);Thread t2 = new Thread(instance2);t1.start();t2.start();}
}

输出结果:

我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束

[!note]

synchronized 用在静态方法上,默认的锁就是当前所在的 Class 类,所以无论是哪个线程访问它,需要的锁都只有一把

2 、 synchronized 修饰 Class 对象

public class SynchronizedObjectLock implements Runnable {static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();@Overridepublic void run() {// 所有线程需要的锁都是同一把synchronized(SynchronizedObjectLock.class){System.out.println("我是线程" + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "结束");}}public static void main(String[] args) {Thread t1 = new Thread(instance1);Thread t2 = new Thread(instance2);t1.start();t2.start();}
}

输出结果:

我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束

[!note]

synchronized (SynchronizedObjectLock. class)锁的是当前类。

2、 synchronized 的原理

2.1、Monitor

Monitor 被翻译为监视器,是由 jvm 提供,c++语言实现
在代码中想要体现monitor需要借助javap命令查看clsss的字节码,比如以下代码:

public class SyncTest {  
​  static final Object lock = new Object();  static int counter = 0;  public static void main(String[] args) {  synchronized (lock) {  counter++;  }  }  
}

找到这个类的class文件,在class文件目录下执行javap -v SyncTest.class,反编译效果如下:
image-20230504165342501

monitorenter 上锁开始的地方
monitorexit 解锁的地方
其中被 monitorenter 和 monitorexit 包围住的指令就是上锁的代码
有两个monitorexit的原因,第二个monitorexit是为了防止锁住的代码抛异常后不能及时释放锁

在使用了synchornized代码块时需要指定一个对象,所以synchornized也被称为对象锁
monitor主要就是跟这个对象产生关联,如下图
image-20230504165833809

[!note]
上面我们说了,synchronized 锁的是对象,所以说一个锁对应一个对象对应一个 Monitor

Monitor内部具体的存储结构:

  • Owner:存储当前获取锁的线程的,只能有一个线程可以获取
  • EntryList:关联没有抢到锁的线程,处于Blocked状态的线程
  • WaitSet:关联调用了wait方法的线程,处于Waiting状态的线程

具体的流程:

  • 代码进入synchorized代码块,先让lock(对象锁)关联的monitor,然后判断Owner是否有线程持有
  • 如果没有线程持有,则让当前线程持有,表示该线程获取锁成功
  • 如果有线程持有,则让当前线程进入entryList进行阻塞,如果Owner持有的线程已经释放了锁,在EntryList中的线程去竞争锁的持有权(非公平)
  • 如果代码块中调用了wait()方法,则会进去WaitSet中进行等待

总结:

  • Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】
  • 它的底层由monitor实现的,monitor是jvm级别的对象( C++实现),线程获得锁需要使用对象(锁)关联monitor
  • 在monitor内部有三个属性,分别是owner、entrylist、waitset
  • 其中owner是关联的获得锁的线程,并且只能关联一个线程;entrylist关联的是处于阻塞状态的线程;waitset关联的是处于Waiting状态的线程

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Flink SQL 基础操作
  • 注解Spring @AliasFor使用笔记
  • 知识点——样本间独立性,传统表征学习,显式物理连接,隐含交互,噪声,类相关类无关
  • 从零开始的CPP(37)跳跃游戏,动态规划,贪心算法
  • 纷享销客CRM AI产品架构概览、产品特色
  • Github 2024-08-09 开源项目日报 Top10
  • git的一些操作指令
  • 工作随记:oracle中偶发遇到存储过程编辑,删除等卡死问题
  • 下一代 AI 搜索引擎 MindSearch:多智能体 + 系统2,模拟人类认知过程的 AI 搜索引擎
  • 在Ubuntu 18.04上安装和配置pgAdmin 4服务器模式的方法
  • Docker最佳实践(六):安装Nacos
  • 9.动态导航栏怎么做
  • uniapp微信小程序 canvas绘制圆形半透明阴影 createCircularGradient函数不支持透明度部分解决方案
  • 100道C/C++面试题
  • mysql8.4.2数据库做主从复制
  • 【EOS】Cleos基础
  • CentOS从零开始部署Nodejs项目
  • css的样式优先级
  • gulp 教程
  • iOS小技巧之UIImagePickerController实现头像选择
  • Javascript弹出层-初探
  • vue脚手架vue-cli
  • 后端_MYSQL
  • ------- 计算机网络基础
  • 两列自适应布局方案整理
  • 那些被忽略的 JavaScript 数组方法细节
  • 通过几道题目学习二叉搜索树
  • 原生Ajax
  • Oracle Portal 11g Diagnostics using Remote Diagnostic Agent (RDA) [ID 1059805.
  • 【运维趟坑回忆录 开篇】初入初创, 一脸懵
  • 如何在 Intellij IDEA 更高效地将应用部署到容器服务 Kubernetes ...
  • ​queue --- 一个同步的队列类​
  • ​油烟净化器电源安全,保障健康餐饮生活
  • #、%和$符号在OGNL表达式中经常出现
  • (二十九)STL map容器(映射)与STL pair容器(值对)
  • (附源码)spring boot基于小程序酒店疫情系统 毕业设计 091931
  • (力扣)1314.矩阵区域和
  • (三十)Flask之wtforms库【剖析源码上篇】
  • (四) Graphivz 颜色选择
  • (一)Spring Cloud 直击微服务作用、架构应用、hystrix降级
  • (一)u-boot-nand.bin的下载
  • (原创) cocos2dx使用Curl连接网络(客户端)
  • **登录+JWT+异常处理+拦截器+ThreadLocal-开发思想与代码实现**
  • .net core 外观者设计模式 实现,多种支付选择
  • .NET 程序如何获取图片的宽高(框架自带多种方法的不同性能)
  • .NET框架设计—常被忽视的C#设计技巧
  • .NET设计模式(11):组合模式(Composite Pattern)
  • .NET设计模式(8):适配器模式(Adapter Pattern)
  • @javax.ws.rs Webservice注解
  • @RequestParam @RequestBody @PathVariable 等参数绑定注解详解
  • [ vulhub漏洞复现篇 ] ThinkPHP 5.0.23-Rce
  • [2023-年度总结]凡是过往,皆为序章
  • [AUTOSAR][诊断管理][ECU][$37] 请求退出传输。终止数据传输的(上传/下载)
  • [AutoSar]BSW_Com07 CAN报文接收流程的函数调用
  • [BZOJ 1032][JSOI2007]祖码Zuma(区间Dp)