了解Synchronized与Lock的区别

前言:

        在多线程编程中,保证线程安全是至关重要的。Java提供了两种主要的同步机制:synchronized关键字和Lock接口。尽管它们都是为了解决多线程并发访问共享资源的问题,但在使用方式和特性上存在一些显著的差异。

synchronized:

     synchronized是Java中的一个关键字,用于实现多线程同步。synchronized块是Java提供的一种原子性内置锁,Java中的每个对象都可以把它当作一个同步锁来使用,这些Java内置的使用者看不到的锁被称为内部锁,也叫作监视器锁。线程的执行代码在进入synchronized代码块前会自动获取内部锁,这时候其他线程访问该同步代码块时会被阻塞挂起。拿到内部锁的线程会在正常退出同步代码块或者抛出异常后或者在同步块内调用了该内置锁资源的wit系列方法时释放该内置锁。内置锁是排它锁,也就是当一个线程获取这个锁后,其他线程必须等待该线程释放锁后才能获取该锁。另外,由于Java中的线程是与操作系统的原生线程一一对应的,所以当阻塞一个线
程时,需要从用户态切换到内核态执行阻塞操作,这是很耗时的操作,而synchronized的
使用就会导致上下文切换。

用途:

  • 对象级别的同步:通过synchronized修饰实例方法或代码块,使得同一对象的其他线程在进入该方法或代码块前需要获取该对象的锁。这种同步机制可以避免多个线程同时对同一个对象进行修改而导致的数据不一致或错误。

代码块:

  • 多个线程使用同一个对象案例:(串行)
@Slf4j
public class SynchronizedTest {//修饰代码块:作用范围是大括号,作用对象是调用代码块的对象。public void test(String name) {synchronized (this) {for (int i = 1; i <= 10; i++) {log.info("test1 - {} - {}", name, i);}}}//修饰方法:作用范围是整个方法,作用对象是调用对象public synchronized void test2(String name) {for (int i = 1; i <= 5; i++) {log.info("test2 - {} - {}", name, i);}}public static void main(String[] args) {SynchronizedTest synchronizedTest = new SynchronizedTest();ExecutorService executorService = Executors.newFixedThreadPool(4);executorService.execute(() -> {synchronizedTest.test("线程1");});executorService.execute(() -> {synchronizedTest.test("线程2");});}
}

执行结果:串行执行。

  • 多个线程使用不同对象:(交替执行)

@Slf4j
public class SynchronizedTest {int count = 0;//修饰代码块:作用范围是大括号,作用对象是调用代码块的对象。public void test(String name) {synchronized (this) {for (int i = 1; i <= 5; i++) {try{Thread.sleep(3000);}catch (Exception e){}count++;log.info("test - {} - count的值{}",name, count);}}}public static void main(String[] args) {SynchronizedTest synchronizedTest1 = new SynchronizedTest();SynchronizedTest synchronizedTest2 = new SynchronizedTest();ExecutorService executorService = Executors.newFixedThreadPool(4);executorService.execute(() -> {synchronizedTest1.test("线程1");});executorService.execute(() -> {synchronizedTest2.test("线程2");});}
}

执行结果:交替执行。

  • 类级别的同步:使用synchronized修饰静态方法或代码块,使得同一类的其他线程在进入该方法或代码块前需要获取该类的Class对象的锁。这种同步机制可以避免多个线程同时对该类的静态成员进行修改而导致的数据不一致或错误。
@Slf4j
public class SynchronizedTest {//修饰静态方法:作用范围是整个方法,作用于所有对象。public static synchronized void test3(String name) {for (int i = 1; i <= 5; i++) {log.info("test3 - {} - {}", name, i);}}//修饰类:作用范围是synchronized后面括号括起来的部分,作用于所有对象。public static void test4(String name) {synchronized (SynchronizedTest.class) {for (int i = 1; i <= 5; i++) {log.info("test4 - {} - {}", name, i);}}}public static void main(String[] args) {SynchronizedTest synchronizedTest1 = new SynchronizedTest();SynchronizedTest synchronizedTest2 = new SynchronizedTest();ExecutorService executorService = Executors.newFixedThreadPool(4);executorService.execute(() -> {synchronizedTest1.test3("线程1");});executorService.execute(() -> {synchronizedTest2.test3("线程2");});}
}

  • 线程之间的同步:通过synchronized的wait()、notify()、notifyAll()方法实现线程之间的等待和通知机制,以实现多个线程之间的协调与同步。

特点:

  • 隐式加锁:synchronized是隐式的,不需要显式地获取和释放锁。
  • 阻塞式加锁:线程在无法获取锁时会一直等待,直到锁被释放。
  • 异常时自动释放锁:当线程在同步代码块中发生异常时,JVM会自动释放锁,避免死锁。

扩展:

        共享变量内存可见性问题主要是由于线程的工作内存导致的,下面我们来讲解synchronized的一个内存语义,这个内存语义就可以解决共享变量内存可见性问题。synchronized块的内存语义是把在synchronized块内使用到的变量从线程的工作内存中清除,这样在synchronized块内使用到该变量时就不会从线程的工作内存中获取,而是直接从主内存中获取。退出synchronized块的内存语义是把在synchronized块内对共享变量的修改刷新到主内存。

        其实这也是加锁和释放锁的语义,当获取锁后会清空锁块内本地内存中将会被用到的共享变量,在使用这些共享变量时从主内存进行加载,在释放锁时将本地内存中修改的共享变量刷新到主内存。

        除可以解决共享变量内存可见性问题外,synchronized经常被用来实现原子性操作。另外请注意,synchronized关键字会引起线程上下文切换并带来线程调度开销。

volatile:

        上面介绍了使用锁的方式可以解决共享变量内存可见性问题,但是使用锁太笨重,因
为它会带来线程上下文的切换开销。对于解决内存可见性问题,Java还提供了一种弱形式
的同步,也就是使用volatile关键字。该关键字可以确保对一个变量的更新对其他线程马
上可见。当一个变量被声明为volatile时,线程在写入变量时不会把值缓存在寄存器或者
其他地方,而是会把值刷新回主内存。当其他线程读取该共享变量时,会从主内存重新获
取最新值,而不是使用当前线程的工作内存中的值。volatile的内存语义和synchronized有
相似之处,具体来说就是,当线程写入了volatile变量值时就等价于线程退出synchronized
同步块(把写入工作内存的变量值同步到主内存),读取volatile变量值时就相当于进入同
步块(先清空本地内存变量值,再从主内存获取最新值)。

public class VolatileTest {private int value;public synchronized void synchronizedIncr(){value2++;}public synchronized void synchronizedDecr(){value2--;}//----------------------private volatile int value2;public void incr(){value2++;}public void decr(){value2--;}
}

使用synchronized和使用volatile是等价的,都解决了共享变量value的内存可见性问题,但是前者是独占锁,同时只能有一个线程调用incr0方法,其他调用线程会被阻塞,同时会存在线程上下文切换和线程重新调度的开销,这也是使用锁方式不好的地方。而后者是非阻塞算法,不会造成线程上下文切换的开销。

Lock:

        Lock是Java提供的另一种同步机制,它是一个接口,定义了更灵活和高级的同步控制方法。ReentrantLock是Lock接口的唯一实现类。

特点:

  • 显式加锁:Lock需要显式地获取和释放锁,通过lock()和unlock()方法实现。

  • 可中断加锁:Lock支持可中断的加锁,通过lockInterruptibly()方法,线程在等待锁的过程中可以被中断。

  • 支持超时时间的加锁:Lock的tryLock(long time, TimeUnit unit)方法允许线程在指定的时间内尝试获取锁,如果超时未获取到锁,则返回失败。

  • 可判断的锁:Lock提供了lock.tryLock()方法,允许线程在获取锁之前判断锁是否可用。

  • 支持公平锁:ReentrantLock的构造函数可以传入一个布尔值,设置为true时表示创建公平锁,线程将按照申请锁的先后顺序获取锁。

  • Condition接口:Lock提供了Condition接口,可以替代Object的wait()、notify()、notifyAll()方法,实现更灵活的线程间通信。

测试:

public class LockTest {Lock lock = new ReentrantLock();public void test(String name) {lock.lock();for (int i = 0; i < 5; i++) {System.out.println(name + ": ");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}lock.unlock();}public void test1(String name) {try {if (lock.tryLock()) {for (int i = 0; i < 5; i++) {System.out.println(name + ": ");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}lock.unlock();} else {System.out.println(Thread.currentThread().getName() + "未获取锁");}} catch (Exception e) {lock.unlock();}}public static void main(String[] args) {LockTest lockTest = new LockTest();ExecutorService executorService = Executors.newFixedThreadPool(4);executorService.execute(() -> {lockTest.test1("线程1");});executorService.execute(() -> {lockTest.test1("线程2");});}
}

执行结果:

两者对比:

  • 加锁方式:synchronized是隐式的,而Lock是显式的。
  • 锁类型:synchronized是非中断锁、非公平锁,而Lock可以是可中断锁、可判断锁、公平锁。
  • 异常处理:synchronized在发生异常时会自动释放锁,而Lock在发生异常时不会自动释放锁,需要在finally块中手动释放。
  • 使用场景:synchronized适用于少量代码的同步,而Lock适用于大量代码的同步,并且可以通过读锁提高多线程读效率。
  • 灵活性:Lock提供了更丰富的功能和更灵活的控制方式,如可中断加锁、支持超时时间的加锁、Condition接口等。

总的来说,synchronized和Lock各有优缺点,选择哪种同步机制应根据具体的应用场景和需求来决定。

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

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

相关文章

DOM操作和事件监听综合练习:利用JS实现图片轮播

我们经常会看到购物网页上有商品图片在自动循环播放&#xff0c;这就是图片轮播&#xff0c;图片轮播‌是一种常见的网页设计元素&#xff0c;用于在网页上自动切换显示多张图片或内容。它通过JavaScript来实现图片的自动轮播效果&#xff0c;结合HTML和CSS来完成布局和样式设置…

Spark 新作《循序渐进 Spark 大数据应用开发》简介

《循序渐进Spark大数据应用开发》由清华大学出版社出版&#xff0c;已于近期上市。该书基于Spark 3.5.1编写&#xff0c;提供24个实战案例26个上机练习&#xff0c;可谓是目前市面上最新的Spark力作。 本文对《循序渐进Spark大数据应用开发》一书做个大致的介绍。 封面部分 …

【王木头】最大似然估计、最大后验估计

目录 一、最大似然估计&#xff08;MLE&#xff09; 二、最大后验估计&#xff08;MAP&#xff09; 三、MLE 和 MAP 的本质区别 四、当先验是均匀分布时&#xff0c;MLE 和 MAP 等价 五、总结 本文理论参考王木头的视频&#xff1a; 贝叶斯解释“L1和L2正则化”&#xff…

算法|牛客网华为机试41-52C++

牛客网华为机试 上篇&#xff1a;算法|牛客网华为机试21-30C 文章目录 HJ41 称砝码HJ42 学英语HJ43 迷宫问题HJ44 SudokuHJ45 名字的漂亮度HJ46 截取字符串HJ48 从单向链表中删除指定值的节点HJ50 四则运算HJ51 输出单向链表中倒数第k个结点HJ52 计算字符串的编辑距离 HJ41 称砝…

【国产桌面操作系统开发】制作桌面快捷方式

前言 目前使用最广的国产桌面操作系统是麒麟kylin操作系统和统信UOS操作系统&#xff0c;在国产系统上开发应用&#xff0c;需要在桌面提供一个快捷方式给用户使用&#xff0c;国产系统是Linux阵营&#xff0c;与window系统是有差异的。 国产系统桌面 国产系统桌面是一个xxx.d…

AndroidStudio-常用布局

一、线性布局LinearLayout 线性布局内部的各视图有两种排列方式: 1.orientation属性值为horizontal时&#xff0c;内部视图在水平方向从左往右排列。 2.orientation属性值为vertical时&#xff0c;内部视图在垂直方向从上往下排列。 如果不指定orientation属性&#xff0c;…

UEditor(百度开源的在线编辑器,修改版)

dc-UEditor&#xff0c;rich text 富文本编辑器&#xff0c;基于百度UEditor 1.4.3.3-utf8-php版修改。 修复了Uploader.class.php的安全隐患。 新增了以下功能&#xff1a; 1、上传图片是否加水印。 2、新增了单独调用上传的接口。 3、表情本地化&#xff0c;预防百度UEd…

Docker安装部署RabbitMQ

1. Docker环境准备 1.1 安装Docker 在开始Docker安装部署RabbitMQ之前&#xff0c;确保您的系统环境已经满足Docker的运行要求。以下是在不同操作系统上安装Docker的步骤和命令行演示。 对于Linux系统 在基于Debian的系统&#xff08;如Ubuntu&#xff09;上&#xff0c;您…

通义千问API调用测试 (colab-python,vue)

文章目录 代码&#xff08;来自官网&#xff09;colab中用python测试Qwen2.5在官网上查看并确定过期时间这里看到我的免费额度到25年5月在同一个页面&#xff0c;点击API示例 前端调用直接在前端调用的优缺点以vue为例&#xff08;代码是基于官网node.js的代码转换而来&#xf…

BLDC基础知识复习【一】

焊接DDR的时候用镊子轻轻抖动一下&#xff0c;能晃动后复位代表焊接成功&#xff1b;用棉签和洗板水清洗板子&#xff0c;不要用纸擦 无刷没有定子和换向器&#xff0c;转子和定子反过来了&#xff1a; KV值越大&#xff0c;电机转速越大。电机转速 KV * 供电电压 外转子电机…

鸿蒙UI开发——自定义UI绘制帧率

1、概 述 随着设备屏幕的不断演进&#xff0c;当前主流设备采用LTPO屏幕&#xff08;可变刷新率屏幕&#xff09;&#xff0c;此类屏幕支持在多个档位之间切换屏幕帧率。 对于快速变化的内容&#xff0c;如射击游戏&#xff0c;交互动画等&#xff0c;显示帧率越高&#xff0…

递归写斐波那契数

在思考一些C语言编程题的解法时我们经常会碰到的一种算法是递归&#xff0c;递归的字面意思是传递回归&#xff0c;会用例子来解释和运用。 递归 例&#xff1a;在控制台输出指定项数的斐波那契数 斐波那契数列数列是指&#xff1a;1,1,2,3,5,8,13,21,34......从第三项开始等…

手写JDK动态代理实现AOP

AOP底层&#xff1f; AOP&#xff08;Aspect Oriented Programming&#xff0c;面向切面编程&#xff09;在 Java 中的实现有多种方式&#xff0c;其中使用 JDK 动态代理和 CGLIB 代理较为常见。 当你的应用程序遵循面向接口编程的原则时&#xff0c;JDK 动态代理是一个自然的…

Gin框架

GoWeb框架 GIN框架 基于httprouter开发的Web框架 安装与使用 安装 下载并安装GIN go get -u github.com/gin-gonic/gin 示例 package mainimport ("github.com/gin-gonic/gin" )func main() {// 创建一个默认的路由引擎r : gin.Default()// GET&#xff1a;请…

nodejs - nodejs安装步骤

安装 NodeJS 1.下载 NodeJS下载官网&#xff1a;https://nodejs.cn/download/ 2.验证 下载后解压安装&#xff0c;运行如下命令验证安装是否成功&#xff1a; node -v npm -v3.查看默认存放位置 查看npm默认存放位置&#xff0c;运行命令如下&#xff1a; npm get prefix…

Spring Boot框架:计算机课程管理的工程认证之光

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了基于工程教育认证的计算机课程管理平台的开发全过程。通过分析基于工程教育认证的计算机课程管理平台管理的不足&#xff0c;创建了一个计算机管理基于工程教育认…

游戏设计:推箱子【easyx图形界面/c语言】

在之前写程序设计的大作业时&#xff0c;在哔哩哔哩上跟着一个视频的学习的成果【第一个练习的】 今天整理文件的时候看到的&#xff0c;就发出来一下【CSDN和B站都有详细教程】 不是大项目&#xff0c;只有两个界面 这个代码只有两百行不到&#xff0c;但通过这个把基本的运…

C++数学

前言 C算法与数据结构 打开打包代码的方法兼述单元测试 数论&#xff1a;质数、最大公约数、菲蜀定理 组合数学汇总 计算几何 博弈论 曼哈顿距离与切比雪夫距离 红线是哈曼顿距离&#xff0c;绿线是切比雪夫距离。 二维曼哈顿距离转切比雪夫距离 曼哈顿距离&#xff1a;|…

如何安装VMWare Workstation 16虚拟机

1、到VMware官网下载安装包。 2、下一步。 3、勾选同意协议&#xff0c;下一步。 4、更换安装路径&#xff0c;下一步。 5、取消全部勾选&#xff0c;下一步。 6、下一步。 7、安装。 8、等待安装完成。 9、安装完成&#xff0c;启动软件。 10、输入许可证ZF3R0…

光流分析技术

光流分析技术是一种重要的计算机视觉和图像处理技术&#xff0c;它通过分析连续帧图像中像素点的运动轨迹和速度&#xff0c;来捕捉图像中物体的运动和相邻帧之间的位移信息。以下是对光流分析技术的详细介绍&#xff1a; 一、光流的基本概念 光流&#xff08;Optical Flow&am…