[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 表格以三种格式存储…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...

Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...

P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...