多线程---线程同步,线程通信

线程同步

1.概述

线程同步是多线程编程中的一个重要概念,它指的是在多线程环境中,通过一定的机制保证多个线程按照某种特定的方式正确、有序地执行。这主要是为了避免并发问题,如死锁、竞态条件、资源争用等,确保数据的一致性和完整性。

当多个线程共享同一份资源时,由于线程的执行顺序是不确定的,可能会出现线程安全问题。例如,两个线程同时对一个共享变量进行操作,可能会出现预期之外的结果。

如下:

小明和小弘对同一账号取钱,会出现余额为负的情况

package Synchronization;
//操作账户
public class Account {private String cardId;private Double amount;public Account(String cardId, Double amount) {this.cardId = cardId;this.amount = amount;}public void withDrawMoney(Double amount){if(this.amount>=amount){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}this.amount=this.amount-amount;System.out.println(Thread.currentThread().getName()+"来取钱成功!余额为"+this.amount);}else {System.out.println(Thread.currentThread().getName()+"来取钱失败!");}}public String getCardId() {return cardId;}public void setCardId(String cardId) {this.cardId = cardId;}public Double getAmount() {return amount;}public void setAmount(Double amount) {this.amount = amount;}
}

package Synchronization;public class Main {public static void main(String[] args) {Account account = new Account("w2xId", (double) 1000);//初始化账户//实例化小明取钱线程new Thread(new Runnable() {@Overridepublic void run() {account.withDrawMoney((double) 1000);}},"小明").start();//实例化小弘取钱线程new Thread(new Runnable() {@Overridepublic void run() {account.withDrawMoney((double) 1000);}},"小弘").start();}
}

结果:

为了避免这种情况,就需要对线程进行同步,即保证同一时刻只有一个线程可以对共享资源进行操作。

2.线程同步的三种方式

1.同步代码块

在Java中,同步代码块是一种确保线程同步的机制,它允许你指定一段代码只能由一个线程在任何给定时间执行。同步代码块是通过在代码块前加上synchronized关键字和一个锁对象来实现的。这个锁对象可以是任何对象,当线程尝试进入同步代码块时,它必须首先获得这个锁。

关键修改部分

//同步代码块
synchronized (this) {if(this.amount>=amount){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}this.amount=this.amount-amount;System.out.println(Thread.currentThread().getName()+"来取钱成功!余额为"+this.amount);}else {System.out.println(Thread.currentThread().getName()+"来取钱失败!");}
}

这里取this作为锁对象,this指代Account对象,也就是Acount对象为锁对象,且此程序只有一个Account实例化对象。

执行结果

2.同步方法

在Java中,同步方法也是一种实现线程同步的常用方式。通过在方法声明前加上synchronized关键字,可以确保该方法在任何时刻只被一个线程访问。同步方法会隐式地锁定当前实例(this),即如果两个线程同时访问同一个对象的同一个同步方法,那么只有一个线程能够执行该方法,另一个线程必须等待。

关键代码修改如下:

synchronized public void withDrawMoney(Double amount){//同步代码块if(this.amount>=amount){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}this.amount=this.amount-amount;System.out.println(Thread.currentThread().getName()+"来取钱成功!余额为"+this.amount);}else {System.out.println(Thread.currentThread().getName()+"来取钱失败!");}}

3.Lock锁

在Java中,除了使用内置的synchronized关键字实现同步外,还可以使用java.util.concurrent.locks包中提供的Lock接口及其实现类(如ReentrantLock)来实现更灵活和强大的线程同步。Lock接口提供了一种机制,可以显式地获取和释放锁,而不是像synchronized那样隐式地获取和释放锁。

使用Lock接口的主要优势包括:

  1. 灵活性:可以中断正在等待锁的线程,可以尝试获取锁而不必阻塞,以及可以释放锁,即使锁是由其他线程获取的。
  2. 可响应性:相比于synchronized,Lock通常具有更好的响应性,因为它允许更细粒度的锁控制。
  3. 条件支持:Lock接口与Condition接口一起使用,可以实现更复杂的线程同步需求。

Lock锁实现步骤:

1.创建Lock锁

2.加锁

3.解锁

关键代码修改如下:

package Synchronization;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Account {private String cardId;private Double amount;private final Lock lock=new ReentrantLock();//1.创建锁对象public Account(String cardId, Double amount) {this.cardId = cardId;this.amount = amount;}
//    synchronized public void withDrawMoney(Double amount){
//        //同步代码块
//            if(this.amount>=amount){
//                try {
//                    Thread.sleep(1000);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                this.amount=this.amount-amount;
//                System.out.println(Thread.currentThread().getName()+"来取钱成功!余额为"+this.amount);
//            }else {
//                System.out.println(Thread.currentThread().getName()+"来取钱失败!");
//            }
//
//    }
synchronized public void withDrawMoney(Double amount){lock.lock();//2.加锁if(this.amount>=amount){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}this.amount=this.amount-amount;System.out.println(Thread.currentThread().getName()+"来取钱成功!余额为"+this.amount);}else {System.out.println(Thread.currentThread().getName()+"来取钱失败!");}lock.unlock();//3.解锁}//    public void withDrawMoney(Double amount){
//        //同步代码块
//        synchronized (this) {
//            if(this.amount>=amount){
//                try {
//                    Thread.sleep(1000);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                this.amount=this.amount-amount;
//                System.out.println(Thread.currentThread().getName()+"来取钱成功!余额为"+this.amount);
//            }else {
//                System.out.println(Thread.currentThread().getName()+"来取钱失败!");
//            }
//        }
//
//    }public String getCardId() {return cardId;}public void setCardId(String cardId) {this.cardId = cardId;}public Double getAmount() {return amount;}public void setAmount(Double amount) {this.amount = amount;}
}

线程通信

1.概述

生产者消费者模型在线程通信中是一个经典的应用场景。这个模型主要用来解决生产者和消费者之间的同步问题,确保两者之间的顺畅协作。在这个模型中,生产者负责生成数据并将其放入共享缓冲区,而消费者则从缓冲区中取出数据进行处理。

生产者消费者模型的关键点:

  1. 共享缓冲区:通常是一个队列或其他数据结构,用作生产者和消费者之间的通信媒介。生产者将生产的数据放入缓冲区,而消费者从缓冲区中取出数据进行处理。
  2. 同步机制:由于生产者和消费者可能同时访问缓冲区,因此需要一种同步机制来确保数据的一致性和避免竞态条件。这通常通过锁、信号量或其他同步原语来实现。在Java中,可以使用synchronized关键字、Lock接口或BlockingQueue来实现同步。
  3. 生产者和消费者的协作:当缓冲区满时,生产者需要等待缓冲区有空闲空间才能继续生产数据。同样,当缓冲区为空时,消费者需要等待缓冲区中有数据才能继续消费。这种协作通过线程间的通信来实现,生产者通知消费者缓冲区有新数据,消费者通知生产者缓冲区有空闲空间。

2.实例

三个生产者生产包子,两个消费者吃包子,每次生产者将一个包子放到桌子上并通知消费者,消费者拿取包子后通知生产者生产包子。

package Synchronization;import java.util.ArrayList;
import java.util.List;/*** 如果桌子上没有包子,则拿包子线程等待,唤醒其他所有线程* 如果桌子上有包子,则放包子线程等待,唤醒其他所有线程*/
public class Desk {private List<String> list=new ArrayList<>();//放包子,通过同步代码块,保证生产者只有一个在生产包子public synchronized void put(){String name = Thread.currentThread().getName();if(list.size()==0){list.add("生产了一个包子");System.out.println(name+list.get(0));try {Thread.sleep(2000);this.notifyAll();//唤醒所有的线程this.wait();//等待} catch (Exception e) {e.printStackTrace();}}else {this.notifyAll();//唤醒所有的线程try {this.wait();//等待} catch (InterruptedException e) {e.printStackTrace();}}}//拿包子,通过同步代码块,保证只有一个消费者拿取包子public synchronized void get(){String name = Thread.currentThread().getName();if(list.size()==1){list.clear();System.out.println(name+"拿了一个包子");try {this.notifyAll();//唤醒所有的线程this.wait();//等待} catch (Exception e) {e.printStackTrace();}}else {this.notifyAll();//唤醒所有的线程try {this.wait();//等待} catch (InterruptedException e) {e.printStackTrace();}}}
}
package Synchronization;public class CommunicationModel {public static void main(String[] args) {Desk desk=new Desk();//创建三个生产者线程new Thread(new Runnable() {@Overridepublic void run() {while (true){desk.put();}}},"厨师1").start();new Thread(new Runnable() {@Overridepublic void run() {while (true){desk.put();}}},"厨师2").start();new Thread(new Runnable() {@Overridepublic void run() {while (true){desk.put();}}},"厨师3").start();//创建两个消费者线程new Thread(new Runnable() {@Overridepublic void run() {while (true){desk.get();}}},"客人1").start();new Thread(new Runnable() {@Overridepublic void run() {while (true){desk.get();}}},"客人2").start();}
}

3.结果

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

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

相关文章

分布式文件系统 SpringBoot+FastDFS+Vue.js【一】

分布式文件系统 SpringBootFastDFSVue.js【一】 一、分布式文件系统1.1.文件系统1.2.什么是分布式文件系统1.3.分布式文件系统的出现1.3.主流的分布式文件系统1.4.分布式文件服务提供商1.4.1.阿里OSS1.4.2.七牛云存储1.4.3.百度云存储 二、fastDFS2.1.fastDSF介绍2.2.为什么要使…

【STM32】软件SPI读写W25Q64芯片

目录 W25Q64模块 W25Q64芯片简介 硬件电路 W25Q64框图 Flash操作注意事项 状态寄存器 ​编辑 指令集 INSTRUCTIONS​编辑 ​编辑 SPI读写W25Q64代码 硬件接线图 MySPI.c MySPI.h W25Q64 W25Q64.c W25Q64.h main.c 测试 SPI通信&#xff08;W25Q64芯片简介&am…

【C++学习手札】多态:掌握面向对象编程的动态绑定与继承机制(深入)

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;世界上的另一个我 1:02━━━━━━️&#x1f49f;──────── 3:58 &#x1f504; ◀️ ⏸ ▶️ ☰ &am…

Python函数(一)

目录 一、定义函数 &#xff08;一&#xff09;向函数传递信息 &#xff08;二&#xff09;实参和形参 二、传递实参 &#xff08;一&#xff09;位置实参 &#xff08;二&#xff09;关键字实参 &#xff08;三&#xff09;默认值 &#xff08;四&#xff09;等效的函…

Code Composer Studio (CCS) - Comment (注释)

Code Composer Studio [CCS] - Comment [注释] References Add Block Comment: 选中几行代码 -> 鼠标右键 -> Source -> Add Block Comment shortcut key: Ctrl Shift / Remove Block Comment: 选中几行代码->鼠标右键->Source->Remove Block Comment s…

redis为什么使用跳跃表而不是树

Redis中支持五种数据类型中有序集合Sorted Set的底层数据结构使用的跳跃表&#xff0c;为何不使用其他的如平衡二叉树、b树等数据结构呢&#xff1f; 1&#xff0c;redis的设计目标、性能需求&#xff1a; redis是高性能的非关系型&#xff08;NoSQL&#xff09;内存键值数据…

12.QT文件对话框 文件的弹窗选择-QFileDialog

目录 前言&#xff1a; 技能&#xff1a; 内容&#xff1a; 1. 界面 2.信号槽 3.其他函数 参考&#xff1a; 前言&#xff1a; 通过按钮实现文件弹窗选择以及关联的操作 效果图就和平时用电脑弹出的选文件对话框一样 技能&#xff1a; QString filename QFileDialog::ge…

蓝桥杯官网填空题(寻找整数)

问题描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 有一个不超过 10^17 的正整数 n&#xff0c;知道这个数除以 2 至 49 后的余数如下表所示&#xff0c;求这个正整数最小是多少。 运行限制 最大运行时间&#xff1a;…

Pr教程1-8节笔记

第一课 认识PR以及PR的学习方法 学习任务&#xff1a; 1、熟练掌握PR软件&#xff0c;同时掌握剪辑技术以及常用于制作特效的效果器。 2、认识PR软件的名称、主要功能以及用途作用。 3、明白学习PR我们能做些什么以及PR的学习方法。 知识内容&#xff1a; 1、PR是专门用于视…

EasyUI动态加载组件

要实现如下的效果&#xff0c;在表格中显示进度条 主要是需要再次初始化组件&#xff0c;借用ChatGPT的意思是&#xff1a; 在许多 JavaScript UI 框架中&#xff0c;包括 EasyUI&#xff0c;在动态地创建或插入新的 DOM 元素后&#xff0c;通常需要手动初始化相关的组件或特性…

HarmonyOS—状态管理概述

在前文的描述中&#xff0c;我们构建的页面多为静态界面。如果希望构建一个动态的、有交互的界面&#xff0c;就需要引入“状态”的概念。 图1 效果图 上面的示例中&#xff0c;用户与应用程序的交互触发了文本状态变更&#xff0c;状态变更引起了UI渲染&#xff0c;UI从“He…

华为23年9月笔试原题,巨详细题解,附有LeetCode测试链接

文章目录 前言思路主要思路关于f函数的剖析Code就到这&#xff0c;铁子们下期见&#xff01;&#xff01;&#xff01;&#xff01; 前言 铁子们好啊&#xff01;今天阿辉又给大家来更新新一道好题&#xff0c;下面链接是23年9月27的华为笔试原题&#xff0c;LeetCode上面的ha…

论文阅读-Pegasus:通过网络内一致性目录容忍分布式存储中的偏斜工作负载

论文名称&#xff1a;Pegasus: Tolerating Skewed Workloads in Distributed Storage with In-Network Coherence Directories 摘要 高性能分布式存储系统面临着由于偏斜和动态工作负载引起的负载不平衡的挑战。本文介绍了Pegasus&#xff0c;这是一个利用新一代可编程交换机…

cool Node后端 中实现中间件的书写

1.需求 在node后端中&#xff0c;想实现一个专门鉴权的文件配置&#xff0c;可以这样来解释 就是 有些接口需要token调用接口&#xff0c;有些接口不需要使用token 调用 这期来详细说明一下 什么是中间件中间件顾名思义是指在请求和响应中间,进行请求数据的拦截处理&#xf…

解锁Spring Boot中的设计模式—04.桥接模式:探索【桥接模式】的奥秘与应用实践!

桥接模式 桥接模式也称为桥梁模式、接口模式或者柄体&#xff08;Handle and Body&#xff09;模式&#xff0c;是将抽象部分与他的具体实现部分分离&#xff0c;使它们都可以独立地变化&#xff0c;通过组合的方式建立两个类之间的联系&#xff0c;而不是继承。 桥接模式是一种…

《区块链公链数据分析简易速速上手小册》第10章:未来趋势和挑战(2024 最新版)

文章目录 10.1 区块链技术的发展方向10.1.1 基础知识10.1.2 重点案例:构建一个简单的智能合约步骤1: 创建智能合约步骤2: 部署智能合约步骤3: 使用Python与智能合约交互结语10.1.3 拓展案例 1:探索 DeFi 应用准备工作实现步骤步骤1: 获取Compound市场数据步骤2: 分析借贷市场…

给定n个结点m条边的简单无向图,判断该图是否存在鱼形状的子图:有一个环,其中有一个结点有另外两条边,连向不在环内的两个结点。若有,输出子图的连边

题目 思路&#xff1a; #include <bits/stdc.h> using namespace std; #define int long long #define pb push_back #define fi first #define se second #define lson p << 1 #define rson p << 1 | 1 const int maxn 1e6 5, inf 1e18 * 3, maxm 4e4 …

如何基于YAML设计接口自动化测试框架?看完秒会!

在设计自动化测试框架的时候&#xff0c;我们会经常将测试数据保存在外部的文件&#xff08;如Excel、YAML、CSV&#xff09;或者数据库中&#xff0c;实现脚本与数据解耦&#xff0c;方便后期维护。目前非常多的自动化测试框架采用通过Excel或者YAML文件直接编写测试用例&…

【Leetcode刷题笔记】27. 移除元素

原题链接 Leetcode 27. 移除元素 题目 给你一个数组 nums 和一个值 val&#xff0c;你需要原地移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并原地修改输入数组。元素的顺序可以改变。…

【MySQL】学习多表查询和笛卡尔积

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-N8PeTKG6uLu4bJuM {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…