【Java 基础】21 多线程同步与锁

文章目录

    • 1.存在的问题
    • 2.使用同步解决问题
      • 1) synchronized
      • 2) volatile
      • 3) 锁
    • 总结

用多线程过程中,有可能出现 多个线程同时处理(获取或修改等)同一个数据,这个时候就 会发生数据不同步的问题, 因此出现了同步和锁来保证多个线程可以安全的处理同一个数据。

1.存在的问题

例如:火车售票窗口售票,假如我们有 2 个窗口(相当于开启了 2 个线程),同时卖 10 张票
在这里插入图片描述

两个窗口的操作流程都如下:

1)购票者到窗口

2)窗口访问票匣子获取余票数量

3)票匣子返回余票数量

4)售票窗口判断,若存在票(大于 0 )则卖给他

那么,假如票匣子就剩下 1 张票啦!但是不巧的是两个窗口同时查看余票数量,都发现还有 1 张票,**又都卖给了购票者……**这就是多线程存在的数据不同步问题_

示例代码:

public class Demo {public static void main(String[] args) throws InterruptedException {ThreadDemo threadDemo = new ThreadDemo();new Thread(threadDemo,"售票窗口1").start();new Thread(threadDemo,"售票窗口2").start();}
}class ThreadDemo implements Runnable {private int ticketCount = 10;@Overridepublic void run() {String tName = Thread.currentThread().getName();while (true) {if (ticketCount <= 0) {return;}try {Thread.sleep(200);System.out.println(tName + "成功卖了一张票!余票:" + (ticketCount-- - 1));} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

输出结果:

售票窗口1成功卖了一张票!余票:9
售票窗口2成功卖了一张票!余票:8
售票窗口1成功卖了一张票!余票:7
售票窗口2成功卖了一张票!余票:6
售票窗口2成功卖了一张票!余票:5
售票窗口1成功卖了一张票!余票:4
售票窗口2成功卖了一张票!余票:3
售票窗口1成功卖了一张票!余票:2
售票窗口1成功卖了一张票!余票:1
售票窗口2成功卖了一张票!余票:0
售票窗口1成功卖了一张票!余票:-1

2.使用同步解决问题

同步(Synchronization) 是一种协调多个线程执行的机制,它能够确保在同一时刻只有一个线程访问共享资源。主要通过关键字 synchronizedvolatile 以及 锁对象 等手段来实现同步。

1) synchronized

关键字 synchronized 用于修饰方法或代码块,保证在同一时刻只有一个线程能够执行被 synchronized 修饰的代码。以下是两种使用方式:

  • 修饰方法
public synchronized void test() {// 同步的代码块
}
  • 修饰代码块
public void someMethod() {// 非同步的代码块synchronized (lockObject) {// 同步的代码块}// 非同步的代码块
}

2) volatile

关键字 volatile 用于声明变量,保证变量的可见性。被 volatile 修饰的变量对所有线程可见,当一个线程修改了这个变量的值,其他线程能够立即看到修改后的值。

public class Demo {private volatile int ticketCount = 10;
}

3) 锁

Java 提供了很多种锁,常用的有 synchronized 关键字、ReentrantLockRead/Write Lock 等 。

  • ReentrantLock

    它支持可重入锁,允许一个线程多次获取同一把锁。

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;public class Demo {private final Lock lock = new ReentrantLock();public void test() {lock.lock();try {// 同步的代码块} finally {lock.unlock();}}
    }
    

    ReentrantLock 提供了比 synchronized 更丰富的功能,如可中断锁、公平锁、定时锁等

  • Read/Write Lock

    ReadWriteLock 接口定义了读写锁,它包含两个锁,一个用于读操作,一个用于写操作。读写锁允许多个线程同时读取共享资源,但只允许一个线程进行写操作。ReentrantReadWriteLockReadWriteLock 的一个实现类

    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;public class Demo {private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();public void read() {readWriteLock.readLock().lock();try {// 读取共享资源的操作} finally {readWriteLock.readLock().unlock();}}public void write() {readWriteLock.writeLock().lock();try {// 修改共享资源的操作} finally {readWriteLock.writeLock().unlock();}}
    }
    

    读写锁适用于读操作远远多于写操作的场景,可以提高并发性

  • StampedLock

    它是一种读写锁的变种,提供了乐观读锁,可以在读多写少的场景中提供更好的性能

    import java.util.concurrent.locks.StampedLock;public class Demo {private final StampedLock stampedLock = new StampedLock();public void read() {long stamp = stampedLock.tryOptimisticRead();// 乐观读操作if (!stampedLock.validate(stamp)) {// 有写操作发生,转为悲观读stamp = stampedLock.readLock();try {// 悲观读操作} finally {stampedLock.unlockRead(stamp);}}}public void write() {long stamp = stampedLock.writeLock();try {// 写操作} finally {stampedLock.unlockWrite(stamp);}}
    }
    

    StampedLock 提供了更细粒度的控制,并允许在不同的代码路径中执行不同的操作

总结

在多线程编程中,确保线程安全是至关重要的!通过合理使用 synchronized 关键字、volatile 关键字以及 ReentrantLock 等锁机制,可以有效地保护共享资源,避免数据不一致和竞态条件等问题。合理的同步机制不仅能够提高程序的性能,还能够确保程序的正确性。在实际开发中,根据具体场景选择合适的同步和锁机制是编写高效、安全多线程代码的关键。

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

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

相关文章

APP备案(Android) - 获取签名证书公钥、MD5

因为近期刚针对各应用平台对APP备案时间节点要求进行了统一整理&#xff0c;然后隔天就被要求提供一下app相关的的公钥和MD5&#xff0c;虽然很快就解决了这个事情&#xff0c;但忍不住又稍微衍生了一下&#xff0c;但行小步&#xff0c;莫问远方吧 关联Blog APP备案(Android)…

java多线程(二)线程池

目录 java线程池 线程池应用场景&#xff1a; 如何创建线程池&#xff1a; 有什么区别&#xff1a; 不同线程池对应的应用场景 案例 输出结果 java线程池 Java线程池是一种预先创建一定数量的线程&#xff0c;并将任务提交给这些线程执行的机制。线程池可以避免频繁创建…

ExecutorService、Callable、Future实现有返回结果的多线程原理解析

原创/朱季谦 在并发多线程场景下&#xff0c;存在需要获取各线程的异步执行结果&#xff0c;这时&#xff0c;就可以通过ExecutorService线程池结合Callable、Future来实现。 我们先来写一个简单的例子—— public class ExecutorTest {public static void main(String[] ar…

Vulnhub项目:EMPIRE: BREAKOUT

一、靶机地址 靶机地址&#xff1a;Empire: Breakout ~ VulnHub 靶机介绍&#xff1a; 该靶机被定义为简单&#xff0c;但是如果没有找到&#xff0c;那就难度成中等了&#xff01; 二、渗透过程 老三样&#xff0c;发现目标&#xff0c;这里用 arp-scan 确定靶机 ip&#…

Java基础50题:14. 使用方法求最大值(2种方法)

概述 使用方法求最大值。 创建方法求两个数的最大值max2&#xff0c;随后再写一个求3个数的最大值函数max3。 要求&#xff1a; 在max3这个方法中&#xff0c;调用max2函数&#xff0c;来实现3个数的最大值计算。 方法一 【代码】 public class P14 {public static int max…

算法___

文章目录 算法两数之和 算法 两数之和 题目如下图&#xff1a; 我的答案如下图&#xff1a; 我采用的是最笨的思路&#xff0c;直接暴力的两次循环&#xff0c;第一次外循环是取数组的第一个元素&#xff0c;然后内循环会遍历数组后面除第一个的所有元素&#xff0c;然后和…

DDD架构思想专栏二《领域层的决策设计思想详解》

如果不了解DDD基本概念的读者可以去看这篇文章&#xff0c;传送门&#xff1a;DDD架构思想专栏一《初识领域驱动设计DDD落地》-CSDN博客 前言介绍 在上一章节介绍了领域驱动设计的基本概念以及按照领域驱动设计的思想进行代码分层&#xff0c;但是仅仅只是从一个简单的分层结…

【Flink系列三】数据流图和任务链计算方式

上文介绍了如何计算并行度和slot的数量&#xff0c;本文介绍Flink代码提交后&#xff0c;如何生成计算的DAG数据流图。 程序和数据流图 所有的Flink程序都是由三部分组成的&#xff1a;Source、Transformation和Sink。Source负责读取数据源&#xff0c;Transformation利用各种…

Remix IDE 快速开始Starknet

文章目录 一、Remix 项目二、基于Web的开发环境Remix 在线 IDE三、Starknet Remix 插件如何使用使用 Remix【重要】通过 Starknet by Example 学习一、Remix 项目 Remix 项目网站 在以太坊合约开发领域,Remix 项目享有很高的声誉,为各个级别的开发人员提供功能丰富的工具集…

JS中深拷贝与浅拷贝

定义 深拷贝&#xff08;Deep Copy&#xff09;和浅拷贝&#xff08;Shallow Copy&#xff09;是在编程中常用的两种对象复制方式。 浅拷贝&#xff08;Shallow Copy&#xff09;&#xff1a; 浅拷贝是创建一个新的对象&#xff0c;将原始对象的属性值复制到新对象中。如果属…

Smart Link和Monitor Link

Smart Link和Monitor Link简介 Smart Link&#xff0c;又叫做备份链路。一个Smart Link由两个接口组成&#xff0c;其中一个接口作为另一个的备份。Smart Link常用于双上行组网&#xff0c;提供可靠高效的备份和快速的切换机制。 Monitor Link是一种接口联动方案&#xff0c;它…

nodejs流

什么是流 stream 流是用于在 Node.js 中处理流数据的抽象接口。 node:stream 模块提供了用于实现流接口的 API。 什么是流数据 流数据是指一组顺序、大量、快速、连续到达的数据序列&#xff0c;一般情况下数据流可被视为一个随时间延续而无限增长的动态数据集合。流数据应用…

【keil备忘录】2. stm32 keil仿真时的时间测量功能

配置仿真器Trace内核时钟为单片机实际的内核时钟&#xff0c;需要勾选Enable设置&#xff0c;设置完成后Enable取消勾选也可以&#xff0c;经测试时钟频率配置仍然生效&#xff0c;此处设置为48MHZ: 时间测量时必须打开register窗口&#xff0c;否则可能不会计数 右下角有计…

第十四章 : Spring Boot 整合spring-session,使用redis共享

第十四章 &#xff1a; Spring Boot 整合spring-session,使用redis共享 前沿 本文重点讲述&#xff1a;spring boot工程中使用spring-session机制进行安全认证&#xff0c;并且通过redis存储session&#xff0c;满足集群部署、分布式系统的session共享。 基于SPringBoot 2.3.2…

[linux运维] 利用zabbix监控linux高危命令并发送告警(基于Zabbix 6)

之前写过一篇是基于zabbix 5.4的实现文章&#xff0c;但是不太详细&#xff0c;最近已经有两个小伙伴在zabbix 6上操作&#xff0c;发现触发器没有str函数&#xff0c;所以更新一下本文&#xff0c;基于zabbix 6 0x01 来看看效果 高危指令出发问题告警&#xff1a; 发出邮件告…

学好操作系统需要的前置知识

1. 态度&#xff1a;不要等一切都准备好了再前行 如果把一切你可能会说&#xff0c;没有这些基础知识&#xff0c;我每看一篇文章&#xff0c;知识就铺天盖地席卷过来&#xff0c;仿佛每一个知识点都准确地打在了自己的盲点上&#xff0c;这该怎么办呢&#xff1f; 我非常能理…

AI助力智慧农业,基于YOLOv8全系列模型【n/s/m/l/x】开发构建不同参数量级的识别系统

智慧农业随着数字化信息化浪潮的演变有了新的定义&#xff0c;在前面的系列博文中&#xff0c;我们从一些现实世界里面的所见所想所感进行了很多对应的实践&#xff0c;感兴趣的话可以自行移步阅读即可&#xff1a; 《自建数据集&#xff0c;基于YOLOv7开发构建农田场景下杂草…

05 JQuery基础入门

文章目录 一、jQuery介绍1. 简介2. 版本介绍3. 相关网站4. HTML引入方式 二、基础语法1. 顶级对象$2. 与DOM对象转化3. 选择器4. 事件5. 动画6. 修改样式7. 修改属性 一、jQuery介绍 1. 简介 jQuery是JavaScript编程语言底层库&#xff0c;它是一个快速&#xff0c;简洁的Jav…

ERPNext SQL 注入漏洞复现

0x01 产品简介 ERPNext 是一套开源的企业资源计划系统。 0x02 漏洞概述 ERPNext 系统frappe.model.db_query.get_list 文件 filters 参数存在 SQL 注入漏洞,攻击者除了可以利用 SQL 注入漏洞获取数据库中的信息(例如,管理员后台密码、站点的用户个人信息)之外,甚至在高权…

基于springboot实现的仿天猫商城项目

一、系统架构 前端&#xff1a;jsp | js | css | jquery 后端&#xff1a;springboot | mybatis-plus 环境&#xff1a;jdk1.7 | mysql | maven 二、代码及数据库 三、功能介绍 01. web端-首页 02. web端-商品查询 03. web端-商品详情 04. web端-购物车 05. web端-订单…