[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、锁升级、对象占内存大小

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/146468.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构:二叉树(2)

ps&#xff1a;爆更第二期 前言 普通的树的实用价值比较小&#xff0c;将树更一步特殊化成二叉树&#xff0c;将获得更多的特殊性质。 例如搜索二叉树&#xff0c;红黑树等。 这篇博文主要介绍二叉树的基础知识&#xff0c;进阶版高级二叉树&#xff0c;后续会持续更新。 二叉…

RK3568平台(基础篇)万用表的使用

一.万用表的通断判断 万用表两个笔头的插法:黑笔头是插在COM的孔里面,红色笔头可以插在其他的三个孔里面,20A和mA分别用来测电流,另外一个孔可以用来测其他(电压 电阻)。 以下这个三角符号(像wifi一样的)可以用来测通断: 使用万用表的红笔和黑笔进行短接,这时候两端…

PAT (Advanced Level) Practice——1020Tree Traversals

链接&#xff1a; 1020 Tree Traversals - PAT (Advanced Level) Practice (pintia.cn) 题目大意&#xff1a; 首先给出一个整数n&#xff0c;表示序列一共有多少个数。接下来给出一棵树的后序遍历和中序遍历&#xff0c;根据后序遍历和中序遍历给出层序遍历。 题解&#x…

【技术调研】三维(7)-Unity基础笔记

安装 ​ 最好使用长期维护版本。 创建项目 ​ 略 窗口布局 Hierarchy:层级面板,展示当前打开的场景里面有哪些物体。 Scene:场景面板,显示当前场景的样子 Game:游戏面板,场景运行的时候的样子 Inspector:检视面板(或属性面板),查看一个游戏物体由哪些组件组成。 …

德勤校招网申笔试综合能力测试SHL题库与面试真题攻略

德勤的综合能力测试&#xff08;General Ability&#xff09;是其校园招聘在线测评的关键环节&#xff0c;旨在评估应聘者的多项认知能力。以下是对这部分内容的全面整合&#xff1a; 综合能力测试&#xff08;General Ability&#xff09; 测试时长为46分钟&#xff0c;包含…

9.3Otsu阈值分割

基本概念 在OpenCV中&#xff0c;Otsu阈值分割是一种全局阈值分割方法&#xff0c;但它会自动选择一个最佳的阈值来分割图像&#xff0c;这个阈值是通过最小化类内方差或等价地最大化类间方差来确定的。OpenCV提供了cv::threshold函数来实现这一功能&#xff0c;其中可以指定c…

线段树-认识线段树+实现线段树

一、认识线段树 1、定义 线段树是平衡二叉树 2、特点 线段树将一个区间划分成单元区间&#xff0c;每个单元区间对应线段树中的一个结点 3、应用 频繁查找一个数组中指定区间内的和、最值 学了动态规划后使用迭代要好过使用递归&#xff0c;因为递归每次进去是有空间损耗…

如何在qtcreator debugger上运行gdb命令

How to run gdb commands from qtcreator debugger? | Qt Forum gdb 调试基础操作和在qtcreator中使用gdb调试_qt gdb-CSDN博客 输出变量名&#xff1a; p变量名 ------------ gdb调试技巧&#xff08;二&#xff09;———— gdb 条件断点_gdb设置带函数入参判断的条件断点…

UE Asset Batch Duplication插件

目录 准备工作 "Scripting library" 三个最重要的功能&#xff08;前两个是UEditorUtilityLibrary中的&#xff09; 自动创建声明&#xff1a; TArray T 的含义 F 的含义 Live Coding &#xff08;Ctrlalt F11&#xff09; Live Coding 的工作流程&#xff…

时序预测|基于灰狼优化LightGBM的时间序列预测Matlab程序GWO-LightGBM 单变量和多变量 含基础模型

时序预测|基于灰狼优化LightGBM的时间序列预测Matlab程序GWO-LightGBM 单变量和多变量 含基础模型 文章目录 一、基本原理原理概述流程注意事项 二、实验结果三、核心代码四、代码获取五、总结 一、基本原理 时序预测中使用灰狼优化&#xff08;GWO&#xff09;结合LightGBM的…

Hash-通过哈希桶解决Hash冲突

哈希桶 基本结构 template<class T> struct HashNode {T _data;HashNode<T>* _next; }; template<class K,class T,class KeyOfT> class HashTable {typedef HashNode<T> Node; public:private:vector<Node*> _tables;size_t _num; }; insert …

《飞机大战游戏》实训项目(Java GUI实现)(设计模式)(简易)

目录 一、最终实现后&#xff0c;效果如下。 &#xff08;1&#xff09;简单介绍本游戏项目&#xff08;待完善&#xff09; &#xff08;2&#xff09;运行效果图&#xff08;具体大家自己可以试&#xff09; 初始运行情况。 手动更换背景图。 通过子弹攻击敌机&#xff0c;累…

java之单链表的基本概念及创建

1.链表的概念: 链表是一种 物理存储结构上非连续 存储结构&#xff0c;数据元素的 逻辑顺序 是通过链表中的 引用链接 次序实现的 。 组成结构: 由一系列节点组成&#xff0c;每个节点包含数据域和指向下一个节点的指针。 优点: 动态大小&#xff0c;易于插入和删除操作。 缺点…

无人机集群路径规划:​北方苍鹰优化算法(Northern Goshawk Optimization,NGO)​求解无人机集群路径规划,提供MATLAB代码

一、单个无人机路径规划模型介绍 无人机三维路径规划是指在三维空间中为无人机规划一条合理的飞行路径&#xff0c;使其能够安全、高效地完成任务。路径规划是无人机自主飞行的关键技术之一&#xff0c;它可以通过算法和模型来确定无人机的航迹&#xff0c;以避开障碍物、优化…

51单片机——LED灯篇

一、LED与单片机P2管脚相连 二、点亮一个LED灯 #include <STC89C5xRC.H> void main() { P2 0xFE; //1111 1110 } P2有8个管脚&#xff0c;对应8个二进制位。 LED灯右侧接电源是正极&#xff08;1&#xff09;&#xff0c;左侧给负极&#xff08;0&#xff09;即可…

Web_php_include 攻防世界

<?php show_source(__FILE__); echo $_GET[hello]; $page$_GET[page]; while (strstr($page, "php://")) { 以是否检测到php://为判断执行循环$pagestr_replace("php://", "", $page);//传入空值&#xff0c;替换 } include($page); ?&g…

第四范式发布AIGS Builder企业级软件重构助手,以生成式AI重构企业软件

产品上新 Product Release 今天&#xff0c;第四范式发布企业级软件重构助手——AIGS Builder&#xff0c;可快速重构软件交互体验。传统的企业软件开发&#xff0c;每次迭代通常要以月计。基于第四范式AIGS Builder大模型&#xff0c;用生成式Agent替代复杂的界面&#xff0c;…

AI绘制调整虚线教程

1、打开ai的软件&#xff0c;执行菜单栏中的文件—新建&#xff0c;新建一个大小任意的画板&#xff0c;画板大小根据自己的需要来设置。 2、选择工具箱中的直线段工具&#xff0c;将填充设置为无&#xff0c;描边设置为黑色&#xff0c;描边大小稍微设置大一点&#xff0c;画一…

模拟实现STL的stack、queue、deque等的介绍

文章目录 前言一、模拟实现stack二、模拟实现queue三、 deque总结 前言 模拟实现STL的stack、queue、deque等的介绍 一、模拟实现stack STL的stack是通过增加一个容器的模板参数&#xff0c;不直接实现栈&#xff0c;让容器存储数据&#xff0c;并调用容器的接口实现栈 name…

环形链表问题——力扣141,142

环形链表问题——力扣141&#xff0c;142 141.判断链表是否带环142.给定一个链表&#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 NULL 141.判断链表是否带环 这道题不能用比较链表中的值来判断是否带环&#xff0c;因为链表中不同节点的值可以相同…