[Java并发编程] synchronized(含与ReentrantLock的区别)

news2024/11/15 18:55:51

文章目录

  • 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;
        }
        @Override
        public 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 {
        @Override
        public 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 {
        @Override
        public 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;
        }

        @Override
        public 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.coloradmin.cn/o/2154610.html

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

相关文章

React组件如何暴露自身的方法

一、研究背景 最近遇到一个如何暴露React组件自身方法的问题。在某些时候&#xff0c;我们需要调用某个组件内部的方法以实现某个功能&#xff0c;因此我们需要了解如何暴露组件内部API的方法。 二、实践过程 本文主要介绍React组件暴露子组件API的方法&#xff0c;以下是实…

2024年研赛-华为杯数模竞赛C题论文首发+论文讲解+代码分享

2024年华为杯-研赛分享资料&#xff08;论文分享部分代码&#xff09;&#xff08;已更新部分代码&#xff09;&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1HGIYjV3lqzUc_3H0vg5H8w 提取码&#xff1a;sxjm 题 目&#xff1a; _基于数据驱动下磁性元件的磁芯损耗建模…

leetcode第十三题:罗马数字转整数

罗马数字包含以下七种字符: I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M。 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如&#x…

OpenCV特征检测(7)角点检测函数goodFeaturesToTrack()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 确定图像上的强角点。 该函数根据 240中所描述的方法查找图像中最显著的角点或者指定图像区域内的最显著角点。 函数使用 cornerMinEigenVal 或…

华南理工大学信息工程高频电子线路课程设计——基于锁相环的调试解调器设计

完整报告链接如下&#xff1a; 通过网盘分享的文件&#xff1a;高频课设报告.docx 链接: https://pan.baidu.com/s/1J83UCDSU0UHcv4ONYxfyhg?pwdzqyr 提取码: zqyr --来自百度网盘超级会员v5的分享 懒得贴上来了&#xff0c;放一下截图。

ICM20948 DMP代码详解(34)

接前一篇文章&#xff1a;ICM20948 DMP代码详解&#xff08;33&#xff09; 上一回解析了inv_icm20948_initialize_lower_driver函数中设置FIFO_RST和FIFO_CFG寄存器相关的代码&#xff0c;本回继续往下解析inv_icm20948_initialize_lower_driver函数的后续代码。为了便于理解和…

蓝桥杯【物联网】零基础到国奖之路:十. OLED

蓝桥杯【物联网】零基础到国奖之路:十.OLED 第一节 硬件解读第二节 MDK配置 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/fa7660b81be9407aa19c603561553db0.png)第三节 代码 第一节 硬件解读 OLED硬件知识: 第二节 MDK配置 第三节 代码 include头文件。 编…

Webui 显卡有显存,会报错:CUDA out of memory

Webui 显卡明明有显存&#xff0c;会报错&#xff1a;CUDA out of memory 网上找了很多资料&#xff0c;都没有能解决这个问题 &#xff0c;后来发现和电脑虚拟内存设置有关&#xff0c;这里记录一下具体的解决方法&#xff1a; 什么是 CUDA Out of Memory 错误&#xff1f; …

【MySQL】字符集与Collation

今天做项目&#xff0c;突然发现&#xff0c;项目中使用的MySQL的库排序规则是 utf8mb4_general_ci&#xff0c;而我自己用的MySQL8默认库规则是utf8mb4_0900_ai_ci&#xff0c;于是想要弄清楚 出处&#xff08;写的非常详细&#xff09;&#xff1a;mysql设置了utf8mb4&#x…

WPF自定义Dialog模板,内容用不同的Page填充

因为审美的不同&#xff0c;就总有些奇奇怪怪的需求&#xff0c;使用框架自带的对话框已经无法满足了&#xff0c;这里记录一下我这边初步设计的对话框。别问为啥要用模板嵌套Page来做对话框&#xff0c;问就是不想写太多的窗体。。。。 模板窗体&#xff08;XAML&#xff09;…

面试-设计模式

策略模式 定义了一组算法&#xff0c;分别封装起来&#xff0c;这些算法直接可以相互替换 设计模式的开闭原则&#xff1a;对修改关闭&#xff0c;对扩展开放 装饰模式 将某种算法作为一个装饰品添加到对象身上&#xff0c;同时可以自由穿戴更换装饰品 两个主要的角色&…

C语言 | Leetcode C语言题解之第423题从英文中重建数字

题目&#xff1a; 题解&#xff1a; char * originalDigits(char * s) {int lenstrlen(s);int arr[26]{0},num[10]{0},cot0;for(int i 0; i < len; i)arr[s[i] - a];num[0] arr[z-a];num[2] arr[w-a];num[4] arr[u-a];num[6] arr[x-a];num[8] arr[g-a];num[1] arr[o…

[JavaEE] UDP协议

目录 再谈端口号 一、端口号的划分 二、UDP协议 三、UDP的特点 再谈端口号 一、端口号的划分 0-1023&#xff1a;知名端口号&#xff0c;端口号固定&#xff0c;其中包括HTTP&#xff0c;FTP&#xff0c;SSH等广为使用的应用层协议。 1024-65535&#xff1a;操作系统动态分…

演示jvm锁存在的问题

文章目录 1、AlbumInfoApiController --》testLock()2、redis添加键值对3、AlbumInfoServiceImpl --》testLock() 没有加锁4、使用ab工具测试4.1、安装 ab 工具4.2、查看 redis 中的值 5、添加本地锁 synchronized6、集群情况下问题演示 jvm锁&#xff1a;synchronized lock 只…

面试金典题2.4

给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你不需要 保留 每个分区中各节点的初始相对位置。 示例 1&#xff1a; 输入&#xff1a;head [1,4,3,2,5,2], x 3 输出&a…

LeetCode从入门到超凡(二)递归与分治算法

引言 大家好&#xff0c;我是GISer Liu&#x1f601;&#xff0c;一名热爱AI技术的GIS开发者。本系列文章是我跟随DataWhale 2024年9月学习赛的LeetCode学习总结文档&#xff1b;在算法设计中&#xff0c;递归和分治算法是两种非常重要的思想和方法。它们不仅在解决复杂问题时表…

matlab绘制二维云图,划分区域,并显示每个区域的均值

绘制成图如下&#xff1a; 代码如下&#xff1a; %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%创建绘图的数据 ax0;bx1; ay0;by1; nx100; %数据的x轴点数 ny100; %数据的y轴点数 hx(bx-ax)/(nx-1); hy(by-ay)/(ny-1); Xax:hx:bx; Yay:hy:by; da…

HTTP中的301、302实现重定向

HTTP状态码301和302描述 ‌HTTP状态码301和302用于实现重定向‌&#xff0c;其中301代表永久重定向&#xff0c;而302代表临时重定向。这两种重定向方式在网页开发、搜索引擎优化&#xff08;SEO&#xff09;以及用户体验方面扮演着重要的角色。 301 301永久重定向‌意味着原…

UDS进阶篇

小结&#xff1a;工欲善其事必先利其器&#xff0c;参考成熟的UDS工具&#xff0c;开发及完善控制器UDS诊断配置。 对应到AUTOSAR中&#xff0c;DEM和DCM&#xff0c;利用工具可实现诊断开发标准流程化&#xff0c;从诊断需求到诊断仪及诊断诊断一条龙开发&#xff0c;不断完善…

★ C++进阶篇 ★ 二叉搜索树

Ciallo&#xff5e;(∠・ω< )⌒☆ ~ 今天&#xff0c;我将继续和大家一起学习C进阶篇第三章----二叉搜索树 ~ ❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️ 澄岚主页&#xff1a;椎名澄嵐-CSDN博客 C基础篇专栏&#xff1a;★ C基础篇 ★_椎名澄嵐的博客-CSD…