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

[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的区别

区别点synchronizedReentrantLock
是什么?关键字,是 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可以限制对共享资源的访问,它锁定的并不是临界资源,而是某个对象,只有线程获取到这个对象的锁才能访问临界区,进而访问临界区中的资源。
  • 保证线程安全
    • 当多个线程去访问同一个类(对象或方法)的时候,该类都能表现出正常的行为(与自己预想的结果一致),那我们就可以说这个类是线程安全的。

    • 造成线程安全问题的主要诱因有两点

      1. 存在共享数据(也称临界资源)
      2. 存在多条线程共同操作共享数据
    • 当存在多个线程操作共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再进行,这种方式有个高尚的名称叫互斥锁,即能达到互斥访问目的的锁,也就是说当一个共享数据被当前正在访问的线程加上互斥锁后,在同一个时刻,其他线程只能处于等待的状态,直到当前线程处理完毕释放该锁。在 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();}
}

结论:

  1. 线程t1、t2、t3中调用的方法都需要获取d1的锁,所以他们是互斥的
  2. t1/t2/t3这3个线程和t4不互斥,他们可以同时运行,因为前面三个线程依赖于d1的锁,t4依赖于d2的锁
  3. 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、锁升级、对象占内存大小

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 常见的中间件漏洞
  • JVM 调优篇7 调优案例2-元空间的优化解决
  • 【什么是B/S、C/S架构】
  • 408算法题leetcode--第11天
  • nginx架构篇(三)
  • PHP环境搭建
  • 数据结构--第七章查找
  • 机器翻译之Bahdanau注意力机制在Seq2Seq中的应用
  • Linux操作系统面试题记录
  • 国内可以使用的ChatGPT服务【9月持续更新】
  • 机器之心 | 阿里云Qwen2.5发布!再登开源大模型王座,Qwen-Max性能逼近GPT-4o
  • 【研发日记】嵌入式处理器技能解锁(六)——ARM的Cortex-M4内核
  • 新书速览|NestJS全栈开发解析:快速上手与实践
  • 数据结构之二叉树查询
  • JetLinks物联网学习(前后端项目启动)
  • [微信小程序] 使用ES6特性Class后出现编译异常
  • Bootstrap JS插件Alert源码分析
  • javascript从右向左截取指定位数字符的3种方法
  • node-sass 安装卡在 node scripts/install.js 解决办法
  • orm2 中文文档 3.1 模型属性
  • React+TypeScript入门
  • Tornado学习笔记(1)
  • vue 配置sass、scss全局变量
  • vue-cli在webpack的配置文件探究
  • Webpack入门之遇到的那些坑,系列示例Demo
  • win10下安装mysql5.7
  • 大整数乘法-表格法
  • 计算机常识 - 收藏集 - 掘金
  • ------- 计算机网络基础
  • 聊聊spring cloud的LoadBalancerAutoConfiguration
  • 爬虫进阶 -- 神级程序员:让你的爬虫就像人类的用户行为!
  • 一个完整Java Web项目背后的密码
  • 自定义函数
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • 你对linux中grep命令知道多少?
  • 第二十章:异步和文件I/O.(二十三)
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • ​2020 年大前端技术趋势解读
  • ‌‌雅诗兰黛、‌‌兰蔻等美妆大品牌的营销策略是什么?
  • #if等命令的学习
  • #多叉树深度遍历_结合深度学习的视频编码方法--帧内预测
  • (2)(2.10) LTM telemetry
  • (day6) 319. 灯泡开关
  • (el-Date-Picker)操作(不使用 ts):Element-plus 中 DatePicker 组件的使用及输出想要日期格式需求的解决过程
  • (k8s)Kubernetes本地存储接入
  • (ZT)出版业改革:该死的死,该生的生
  • (附表设计)不是我吹!超级全面的权限系统设计方案面世了
  • (算法)Game
  • (一)Spring Cloud 直击微服务作用、架构应用、hystrix降级
  • (原創) 如何刪除Windows Live Writer留在本機的文章? (Web) (Windows Live Writer)
  • *setTimeout实现text输入在用户停顿时才调用事件!*
  • .net core 连接数据库,通过数据库生成Modell
  • .NET Core中Emit的使用
  • .NET delegate 委托 、 Event 事件,接口回调
  • .net 流——流的类型体系简单介绍