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

线程同步

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.hqwc.cn/news/474224.html

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

相关文章

MySQL数据库进阶第一篇(存储引擎与Linux系统上安装MySQL数据库)

文章目录 一、MySQL体系结构二、存储引擎介绍相关操作&#xff1a; 三、InnoDB 存储引擎简介四、MyISAM 存储引擎简介五、Memory 存储引擎简介六、存储引擎特点七、存储引擎的选择八、Linux系统上安装MySQL数据库1.用FinalShell远程连接Linux系统2.下载Linux版的MySQL安装包3.上…

蓝桥杯备赛_python_BFS搜索算法_刷题学习笔记

1 bfs广度优先搜索 1.1 是什么 1.2怎么实现 2案例学习 2.1.走迷宫 2.2.P1443 马的遍历 2.3. 九宫重排&#xff08;看答案学的&#xff0c;实在写不来&#xff09; 2.4.青蛙跳杯子&#xff08;学完九宫重排再做bingo&#xff09; 2.5. 长草 3.总结 1 bfs广度优先搜索 【P…

模拟算法.

1.什么是模拟 在信息奥赛中,有一类问题是模拟一个游戏的对弈过程或者模拟一项任务的操作过程.比如乒乓球在比赛中模拟统计记分最终判断输赢的过程等等,这些问题通常很难通过建立数学模型用特定的算法来解决因为它没有一种固定的解法,需要深刻理解出题者对过程的解释一般只能采…

清除Django的管理员admin站点中“Recent Actions“最近活动面板上的所有信息

清除Django的管理员admin站点中"Recent Actions"最近活动面板上的所有信息 本文主要介绍了如何清除Django的管理员admin站点中"Recent Actions"最近活动面板上的所有信息 操作步骤如下 进入Django项目目录中运行代python manage.py shell进入Django shell…

抓包工具Wireshark怎么使用

抓包工具Wireshark大家都知道&#xff0c;它可以截获和分析网络数据封包&#xff0c;检测网络上的问题&#xff0c;比如网络延迟、数据丢失、拥堵等&#xff0c;以及评估网络性能。 当网络里发现恶意攻击、某人下载流量过大、设备互联丢包、协议交互失败等等情况时&#xff0c…

VMwareWorkstation17.0虚拟机安装搭建Windows 11虚拟机(完整图文详细步骤教程)

VMwareWorkstation17.0虚拟机安装搭建Windows 11虚拟机&#xff08;完整图文详细步骤教程&#xff09; 一、下载Windows11二、配置Windows11虚拟机机器环境三、启动Windows11系统 一、下载Windows11 【点击打开最全面的Windows 11原版系统镜像下载地址】 https://blog.csdn.ne…

import tensorflow_hub报错

问题&#xff1a; 导入tensorflow_hub报ModuleNotFoundError: No module named ‘tensorflow.python.checkpoint’ 解决&#xff1a; tensorflow-estimator版本不对 和tensorflow&#xff08;2.6.0&#xff09;版本一致 。 pip install -U tensorflow-estimator2.6.0 验证&a…

函数、极限、连续——刷题(7

目录 1.题目&#xff1a;2.解题思路和步骤&#xff1a;3.总结&#xff1a;小结&#xff1a; 1.题目&#xff1a; 2.解题思路和步骤&#xff1a; 记住和差化积、积化和差公式即可&#xff1a; 然后就可以化简&#xff1a; 3.总结&#xff1a; 记住和差化积、积化和差公式即可…

rust函数 stuct struct方法 关联函数

本文结合2个代码实例主要介绍了rust函数定义方法&#xff0c;struct结构体定义、struct方法及关联函数等相关基础知识。 代码1&#xff1a; main.rc #[derive(Debug)]//定义一个结构体 struct Ellipse {max_semi_axis: u32,min_semi_axis: u32, }fn main() {//椭圆&#xff0…

数据结构-邻接矩阵

介绍 邻接矩阵&#xff0c;是表示图的一种常见方式&#xff0c;具体表现为一个记录了各顶点连接情况的呈正方形的矩阵。 假设一共有以下顶点&#xff0c;其连接关系如图所示 那么&#xff0c;怎么表示它们之间的连接关系呢&#xff1f; 我们发现&#xff0c;各条边所连接的都…

安装luajit及使用python运行lua脚本

使用Python运行lua脚本前&#xff0c;需要先安装LuaJIT&#xff0c;LuaJIT的官网是下载 (luajit.org) 目前已不再使用.exe文件的下载方式&#xff0c;需要使用Git从公共仓库下载源码&#xff0c;git命令为&#xff1a; $ git clone https://luajit.org/git/luajit.git 下载后…

泰华仪表 智能热电偶温度变送器一进二出K型隔离模拟量输出4-20mA

品牌&#xff1a;泰工华控 型号&#xff1a;TSP-TC 产地&#xff1a;中国大陆 省份&#xff1a;安徽省 颜色分类&#xff1a;一进二出&#xff08;下单备注参数&#xff09;,K型热电偶转4-20mA(一进一出),K型热电偶转0-5V(一进一出),其他类型ESJBNR等&#xff08;下单备注&…