[Java并发编程] synchronized(含与ReentrantLock的区别)
文章目录
- 1. synchronized与ReentrantLock的区别
- 2. synchronized的作用
- 3. synchronized的使用
- 3.1 修饰实例方法,作用于当前实例,进入同步代码前需要先获取实例的锁
- 3.2 修饰静态方法,作用于类的Class对象,进入修饰的静态方法前需要先获取类的Class对象的锁
- 3.3 修饰代码块,需要指定加锁对象(记做lockobj),在进入同步代码块前需要先获取lockobj的锁
- 4. 分析代码是否互斥
- 5. synchronized的可重入性
- 6. 发生异常synchronized会释放锁
- 7. synchronized的实现原理与应用(包含锁的升级过程)
1. synchronized与ReentrantLock的区别
| 区别点 | synchronized | ReentrantLock |
|---|---|---|
| 是什么? | 关键字,是 JVM 层面通过监视器实现的 | 类,基于 AQS 实现的 |
| 公平锁与否? | 非公平锁 | 支持公平锁和非公平锁,默认非公平锁 |
| 获取当前线程是否上锁 | 无 | 可以(isHeldByCurrentThread()) |
| 条件变量 | 无 | 支持条件变量(newCondition()) |
| 异常处理 | 在 synchronized 块中发生异常,锁会自动释放 | 在 ReentrantLock 中没有在 finally 块中正确地调用 unlock() 方法,则可能会导致死锁 |
| 灵活性1 | 自动加锁和释放锁 | 手动加锁和释放锁 |
| 灵活性2 | 无 | 允许尝试去获取锁而不阻塞(如 tryLock 方法),并且可以指定获取锁等待的时间(如 tryLock(long time, TimeUnit unit))。 |
| 可中断性 | 不可中断,除非发生了异常 | 允许线程中断另一个持有锁的线程,这样持有锁的线程可以选择放弃锁并响应中断。1.tryLock(long timeout, TimeUnit unit);2.lockInterruptibly()和interrupt()配合使用 |
| 锁的内容 | 对象,锁信息保存在对象头中 | int类型的变量来标识锁的状态:private volatile int state; |
| 锁升级过程 | 无锁->偏向锁->轻量级锁->重量级锁 | 无 |
| 使用位置 | 普通方法、静态方法、代码块 | 代码块(方法里的代码,初始化块都是代码块) |
2. synchronized的作用

- 在Java中,使用synchronized关键字可以确保任何时刻只有一个线程可以执行特定的方法或者代码块。这有助于防止数据竞争条件(race conditions)和其他由于线程间共享资源而产生的问题。
- 当一个方法或代码块被声明为synchronized,它意味着在该方法或代码块执行期间,其他试图获得相同锁的线程将被阻塞,直到持有锁的线程释放该锁。这个锁通常是对象的一个监视器(monitor),对于静态方法来说是类的Class对象,对于实例方法则是拥有该方法的对象。
- synchronized可以限制对共享资源的访问,它锁定的并不是临界资源,而是某个对象,只有线程获取到这个对象的锁才能访问临界区,进而访问临界区中的资源。
- 保证线程安全。
-
当多个线程去访问同一个类(对象或方法)的时候,该类都能表现出正常的行为(与自己预想的结果一致),那我们就可以说这个类是线程安全的。
-
造成线程安全问题的主要诱因有两点
- 存在共享数据(也称临界资源)
- 存在多条线程共同操作共享数据
-
当存在多个线程操作共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再进行,这种方式有个高尚的名称叫互斥锁,即能达到互斥访问目的的锁,也就是说当一个共享数据被当前正在访问的线程加上互斥锁后,在同一个时刻,其他线程只能处于等待的状态,直到当前线程处理完毕释放该锁。在 Java 中,关键字 synchronized可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作),同时我们还应该注意到synchronized另外一个重要的作用,synchronized可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代volatile功能)。
-
3. synchronized的使用
下面三种本质上都是锁对象
3.1 修饰实例方法,作用于当前实例,进入同步代码前需要先获取实例的锁
示例代码:
public class SynchronizedDemo2 {int num = 0;public synchronized void add() {
// public void add() {for (int i = 0; i < 10000; i++) {num++;}}public static class AddDemo extends Thread {private SynchronizedDemo2 synchronizedDemo2;public AddDemo(SynchronizedDemo2 synchronizedDemo2) {this.synchronizedDemo2 = synchronizedDemo2;}@Overridepublic void run() {this.synchronizedDemo2.add();}}public static void main(String[] args) throws InterruptedException {// 要想拿到临界资源,就必须先获得到这个对象的锁。SynchronizedDemo2 synchronizedDemo2 = new SynchronizedDemo2();AddDemo addDemo1 = new AddDemo(synchronizedDemo2);AddDemo addDemo2 = new AddDemo(synchronizedDemo2);AddDemo addDemo3 = new AddDemo(synchronizedDemo2);addDemo1.start();addDemo2.start();addDemo3.start();// 阻塞主线程addDemo1.join();addDemo2.join();addDemo3.join();// 打印结果System.out.println(synchronizedDemo2.num);}
}
打印:
期望结果:30000
无synchronized结果:23885
有synchronized结果:30000
synchronize作用于实例方法需要注意:
- 实例方法上加synchronized,线程安全的前提是,多个线程操作的是同一个实例,如果多个线程作用于不同的实例,那么线程安全是无法保证的
- 同一个实例的多个实例方法上有synchronized,这些方法都是互斥的,同一时间只允许一个线程操作同一个实例的其中的一个synchronized方法
3.2 修饰静态方法,作用于类的Class对象,进入修饰的静态方法前需要先获取类的Class对象的锁
锁定静态方法需要通过类.class,或者直接在静态方法上加上关键字。但是,类.class不能使用this来代替。注:在同一个类加载器中,class是单例的,这也就能保证synchronized能够只让一个线程访问临界资源。
示例代码:
public class SynchronizedDemo1 {static int num = 0;// 加上synchronized保证线程安全public static synchronized void add() {// public static void add() {for (int i = 0; i < 10000; i++) {num++;}}// 同上public static void add1() {synchronized (SynchronizedDemo1.class) {for (int i = 0; i < 10000; i++) {num++;}}}public static class AddDemo extends Thread {@Overridepublic void run() {SynchronizedDemo1.add();}}public static void main(String[] args) throws InterruptedException {AddDemo addDemo1 = new AddDemo();AddDemo addDemo2 = new AddDemo();AddDemo addDemo3 = new AddDemo();addDemo1.start();addDemo2.start();addDemo3.start();// 阻塞主线程addDemo1.join();addDemo2.join();addDemo3.join();// 打印结果System.out.println(SynchronizedDemo1.num);}
}
打印:
期望结果:30000
无synchronized结果:14207
有synchronized结果:30000
3.3 修饰代码块,需要指定加锁对象(记做lockobj),在进入同步代码块前需要先获取lockobj的锁
若是this,相当于修饰实例方法
示例代码:
public class SynchronizedDemo3 {private static Object lockobj = new Object();private static int num = 0;public static void add() {synchronized (lockobj) {for (int i = 0; i < 10000; i++) {num++;}}}public static class AddDemo extends Thread {@Overridepublic void run() {SynchronizedDemo3.add();}}public static void main(String[] args) throws InterruptedException {AddDemo addDemo1 = new AddDemo();AddDemo addDemo2 = new AddDemo();AddDemo addDemo3 = new AddDemo();addDemo1.start();addDemo2.start();addDemo3.start();// 阻塞主线程addDemo1.join();addDemo2.join();addDemo3.join();// 打印结果System.out.println(SynchronizedDemo3.num);}
}
打印:
期望结果:30000
无synchronized结果:28278
有synchronized结果:> 示例代码:
4. 分析代码是否互斥
分析代码是否互斥的方法,先找出synchronized作用的对象是谁,如果多个线程操作的方法中synchronized作用的锁对象一样,那么这些线程同时异步执行这些方法就是互斥的。
示例代码:
public class SynchronizedDemo4 {// 作用于当前类的实例对象public synchronized void m1() {}// 作用于当前类的实例对象public synchronized void m2() {}// 作用于当前类的实例对象public void m3() {synchronized (this) {}}// 作用于当前类Class对象public static synchronized void m4() {}// 作用于当前类Class对象public static void m5() {synchronized (SynchronizedDemo4.class) {}}public static class T extends Thread {SynchronizedDemo4 demo;public T(SynchronizedDemo4 demo) {this.demo = demo;}@Overridepublic void run() {super.run();}}public static void main(String[] args) {SynchronizedDemo4 d1 = new SynchronizedDemo4();Thread t1 = new Thread(() -> {d1.m1();});Thread t2 = new Thread(() -> {d1.m2();});Thread t3 = new Thread(() -> {d1.m3();});SynchronizedDemo4 d2 = new SynchronizedDemo4();Thread t4 = new Thread(() -> {d2.m2();});Thread t5 = new Thread(() -> {SynchronizedDemo4.m4();});Thread t6 = new Thread(() -> {SynchronizedDemo4.m5();});t1.start();t2.start();t3.start();t4.start();t5.start();t6.start();}
}
结论:
- 线程t1、t2、t3中调用的方法都需要获取d1的锁,所以他们是互斥的
- t1/t2/t3这3个线程和t4不互斥,他们可以同时运行,因为前面三个线程依赖于d1的锁,t4依赖于d2的锁
- t5、t6都作用于当前类的Class对象锁,所以这两个线程是互斥的,和其他几个线程不互斥
5. synchronized的可重入性
示例代码:
public class SynchronizedDemo5 {synchronized void method1() {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}method2();System.out.println("method1 thread-" + Thread.currentThread().getName() + " end");}synchronized void method2() {try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("method2 thread-" + Thread.currentThread().getName() + " end");}public static void main(String[] args) {SynchronizedDemo5 t5 = new SynchronizedDemo5();new Thread(t5::method1, "1").start();new Thread(t5::method1, "2").start();new Thread(t5::method1, "3").start();}
}
打印:
method2 thread-1 end
method1 thread-1 end
method2 thread-3 end
method1 thread-3 end
method2 thread-2 end
method1 thread-2 end
结论:
当线程启动的时候,已经获取了对象的锁,等method1调用method2方法的时候,同样是拿到了这个对象的锁。所以synchronized是可重入的。
6. 发生异常synchronized会释放锁
示例代码:
public class SynchronizedDemo6 {int num = 0;synchronized void add() {System.out.println("thread" + Thread.currentThread().getName() + " start");while (num <= 7) {num++;System.out.println("thread" + Thread.currentThread().getName() + ", num is " + num);if (num == 3) {throw new NullPointerException();}}}public static void main(String[] args) throws InterruptedException {SynchronizedDemo6 synchronizedDemo6 = new SynchronizedDemo6();new Thread(synchronizedDemo6::add, "1").start();Thread.sleep(1000);new Thread(synchronizedDemo6::add, "2").start();}
}
打印:
thread1 start
thread1, num is 1
thread1, num is 2
thread1, num is 3
Exception in thread “1” java.lang.NullPointerException
at com.xin.demo.threaddemo.lockdemo.synchronizeddemo.SynchronizedDemo6.add(SynchronizedDemo6.java:14)
at java.lang.Thread.run(Thread.java:748)
thread2 start
thread2, num is 4
thread2, num is 5
thread2, num is 6
thread2, num is 7
thread2, num is 8
结论:
发生异常synchronized会释放锁
7. synchronized的实现原理与应用(包含锁的升级过程)
我的另一篇读书笔记:Java并发机制的底层实现原理 2.2节
锁的升级过程:无锁->偏向锁->轻量级锁->重量级锁,详细情况还是看上面这篇文章
- 无锁
- 偏向锁:在锁对象的对象头中记录一下当前获取到该锁的线程ID,该线程下次如果又来获取该锁就可以直接获取到了,也就是支持锁重入
- 轻量级锁:当两个或以上线程交替获取锁,但并没有在对象上并发的获取锁时,偏向锁升级为轻量级锁。在此阶段,线程采取CAS的自旋方式尝试获取锁,避免阻塞线程造成的CPU在用户态和内核态间转换的消耗。轻量级锁时,CPU是用户态。
- 重量级锁:两个或以上线程并发的在同一个对象上进行同步时,为了避免无用自旋消耗CPU,轻量级锁会升级成重量级锁。重量级锁时,CPU是内核态。
参考1:【多线程与高并发】- synchronized锁的认知
参考2:线程安全和synchronized
建议阅读文章1:【并发编程系列2】synchronized锁升级原理分析(偏向锁-轻量级锁-重量级锁)
建议阅读文章2:Java 对象、对象头mark word、锁升级、对象占内存大小
相关文章:
[Java并发编程] synchronized(含与ReentrantLock的区别)
文章目录 1. synchronized与ReentrantLock的区别2. synchronized的作用3. synchronized的使用3.1 修饰实例方法,作用于当前实例,进入同步代码前需要先获取实例的锁3.2 修饰静态方法,作用于类的Class对象,进入修饰的静态方法前需要…...
spring-boot-maven-plugin插件打包和java -jar命令执行原理
文章目录 1. Maven生命周期2. jar包结构2.1 不可执jar包结构2.2 可执行jar包结构 3. spring-boot-maven-plugin插件打包4. 执行jar原理 1. Maven生命周期 Maven的生命周期有三种: clean:清除项目构建数据,较为简单,不深入探讨&a…...
Python办公自动化教程(001):PDF内容提取
1、Pdfplumber介绍 pdfplumber的github地址: https://github.com/jsvine/pdfplumber/【介绍】:pdfplumber 是一个用于处理 PDF 文件的 Python 第三方库,它提供了一种方便的方式来提取 PDF 文件中的文本、表格和其他信息。【功能】ÿ…...
HarmonyOS鸿蒙开发实战(5.0)自定义全局弹窗实践
鸿蒙HarmonyOS开发实战往期文章必看: HarmonyOS NEXT应用开发性能实践总结 最新版!“非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线!(从零基础入门到精通) 非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线&am…...
【AI学习】了解OpenAI o1背后的self-play RL:开启新的智能道路
在ChatGPT刚刚出来的时候,沐神关于ChatGPT有一段视频,只有几分钟,却是讲得极其透彻的一段。大概意思就是,过去的AI智能水平,比如五年前,大概相当于人类5秒钟思考的程度,包括自动驾驶,…...
Java项目实战II基于Java+Spring Boot+MySQL的车辆管理系统(开发文档+源码+数据库)
目录 一、前言 二、技术介绍 三、系统实现 四、论文参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发,CSDN平台Java领域新星创作者,专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 "随着…...
IPsec-VPN中文解释
网络括谱图 IPSec-VPN 配置思路 1 配置IP地址 FWA:IP地址的配置 [FW1000-A]interface GigabitEthernet 1/0/0 [FW1000-A-GigabitEthernet1/0/0]ip address 10.1.1.1 24 //配置IP地址 [FW1000-A]interface GigabitEthernet 1/0/2 [FW1000-A-GigabitEthernet1/0/2]ip a…...
Ubuntu 22.04 源码下载、编译
Kernel/BuildYourOwnKernel - Ubuntu Wikihttps://wiki.ubuntu.com/Kernel/BuildYourOwnKernel 一、查询当前系统内核版本 rootubuntu22:~# uname -r 5.15.0-118-generic 二、查询本地软件包数据库中的内核源码信息 rootubuntu22:~# apt search linux-source Sorting... Do…...
【深度学习实战—11】:基于Pytorch实现谷歌QuickDraw数据集的下载、解析、格式转换、DDP分布式训练、测试
✨博客主页:王乐予🎈 ✨年轻人要:Living for the moment(活在当下)!💪 🏆推荐专栏:【图像处理】【千锤百炼Python】【深度学习】【排序算法】 目录 😺〇、仓库…...
基于SpringBoot+WebSocket实现地图上绘制车辆实时运动轨迹图
实现基于北斗卫星的车辆定位和轨迹图的Maven工程(使用模拟数据),我们将使用以下技术: Spring Boot:作为后端框架,用来提供数据接口。Thymeleaf:作为前端模板引擎,呈现网页。Leaflet…...
嵌入式入门小工程
此代码基于s3c2440 1.点灯 //led.c void init_led(void) {unsigned int t;t GPBCON;t & ~((3 << 10) | (3 << 12) | (3 << 14) | (3 << 16));t | (1 << 10) | (1 << 12) | (1 << 14) | (1 << 16);GPBCON t; }void le…...
hackmyvm靶场--zon
环境 攻击机kali 靶机 未知 主机探测 因为在同一个局域网内使用ARP协议探测存活主机 靶机为192.168.56.128 端口探测 常见的80和22端口 那么一定是寻找web漏洞拿shell了 后台扫描 后台扫描常用dirsearch和gobuster,有时候小字典可能不太行,可以尝试换个大点…...
atcoder abc372 启发式合并, dp
A delete 代码: #include <bits.stdc.h>using namespace std;int main() {string s;cin >> s;for(auto t: s) if(t ! .) cout << t; } B 3 ^ A 思路:三进制转换,可以参考二进制,先把当前可以加入的最大的3的…...
CentOS Stream 9部署MariaDB
1、更新系统软件包 sudo dnf update 2、安装MariaDB软件包(替代mysql) sudo dnf install mariadb-server 3、安装MariaDB服务 sudo systemctl enable --now mariadb 4、检查MariaDB服务状态 sudo systemctl status mariadb 5、配置MariaDB安全性 sudo my…...
【Leetcode:997. 找到小镇的法官 + 入度出度】
🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,…...
大数据Flink(一百二十三):五分钟上手Flink MySQL连接器
文章目录 五分钟上手Flink MySQL连接器 一、创建数据库表 二、创建session集群 三、源表查询 四、窗口计算 五、结果数据写回数据库 五分钟上手Flink MySQL连接器 MySQL Connector可以将本地或远程的MySQL数据库连接到Flink中&#x…...
SYN Flood攻击原理,SYN Cookie算法
SYN Flood是一种非常危险而常见的Dos攻击方式。到目前为止,能够有效防范SYN Flood攻击的手段并不多,SYN Cookie就是其中最著名的一种。 1. SYN Flood攻击原理 SYN Flood攻击是一种典型的拒绝服务(Denial of Service)攻击。所谓的拒绝服务攻击就是通过进…...
计组(蒋)期末速成笔记1
蒋本珊计组期末不挂科复习笔记 第1章 概论 第2章 数据的机器层次表示 第3章 指令系统 第4章 数值的机器运算 第5章 存储系统和结构 第6章 中央处理器 第7章 总线 第1章 概论 蒋本珊计组期末不挂科复习笔记知道你快考试了,莫慌! 第1章 概论1.1 冯诺依曼计…...
mysql学习教程,从入门到精通,SQL 更新数据(UPDATE 语句)(17)
1、SQL 更新数据(UPDATE 语句) SQL UPDATE 需要指定要更新的表、要修改的列以及新值,并且通常会通过WHERE子句来指定哪些行需要被更新。下面是一个简单的示例,说明如何使用UPDATE语句。 假设我们有一个名为employees的表…...
【吊打面试官系列-MySQL面试题】MyISAM 表格将在哪里存储,并且还提供其存储格式?
大家好,我是锋哥。今天分享关于【MyISAM 表格将在哪里存储,并且还提供其存储格式?】面试题,希望对大家有帮助; MyISAM 表格将在哪里存储,并且还提供其存储格式? 每个 MyISAM 表格以三种格式存储…...
RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...
PHP 8.5 即将发布:管道操作符、强力调试
前不久,PHP宣布了即将在 2025 年 11 月 20 日 正式发布的 PHP 8.5!作为 PHP 语言的又一次重要迭代,PHP 8.5 承诺带来一系列旨在提升代码可读性、健壮性以及开发者效率的改进。而更令人兴奋的是,借助强大的本地开发环境 ServBay&am…...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...
认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...
comfyui 工作流中 图生视频 如何增加视频的长度到5秒
comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗? 在ComfyUI中实现图生视频并延长到5秒,需要结合多个扩展和技巧。以下是完整解决方案: 核心工作流配置(24fps下5秒120帧) #mermaid-svg-yP…...
JDK 17 序列化是怎么回事
如何序列化?其实很简单,就是根据每个类型,用工厂类调用。逐个完成。 没什么漂亮的代码,只有有效、稳定的代码。 代码中调用toJson toJson 代码 mapper.writeValueAsString ObjectMapper DefaultSerializerProvider 一堆实…...
