Java线程通信:原理与简单示例

Java线程通信:原理与简单示例

在Java中,线程之间的通信是一个非常重要的概念。这通常涉及到等待、通知和阻塞等机制。在多线程环境中,线程间的正确通信可以确保程序的流程顺利进行,数据的安全访问和共享。下面我们将深入探讨Java中的线程通信方式及其原理。

1. 共享内存模型


在Java中,所有线程共享内存,这为线程间的通信提供了基础。我们可以使用共享变量来在不同的线程之间共享数据。然而,对于并发访问共享变量,我们需要注意同步问题,以防止数据的竞态条件和不一致。

1.1 示例:两个线程交换数据

下面的示例显示了两个线程如何通过共享变量交换数据。我们使用synchronized关键字来确保同步访问。

public class SharedData {private int data;public synchronized void setData(int data) {this.data = data;}public synchronized int getData() {return data;}
}public class ThreadA extends Thread {private SharedData sharedData;public ThreadA(SharedData sharedData) {this.sharedData = sharedData;}public void run() {int temp = sharedData.getData();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}sharedData.setData(temp + 10);}
}public class ThreadB extends Thread {private SharedData sharedData;public ThreadB(SharedData sharedData) {this.sharedData = sharedData;}public void run() {int temp = sharedData.getData();System.out.println("ThreadB: " + temp);sharedData.setData(temp + 5);}
}public class Main {public static void main(String[] args) {SharedData sharedData = new SharedData();ThreadA threadA = new ThreadA(sharedData);ThreadB threadB = new ThreadB(sharedData);threadA.start();threadB.start();}
}

在上面的代码中,我们创建了两个线程(ThreadAThreadB),它们都共享一个SharedData对象。ThreadA先获取SharedData对象的数据,等待一秒钟,然后将数据增加10。与此同时,ThreadB也获取数据,打印出来,并将数据增加5。虽然两个线程都在修改数据,但因为使用了synchronized关键字进行同步,所以不会出现数据不一致的情况。

2. 等待/通知机制


Java中的等待/通知机制允许线程暂停执行(等待)直到另一个线程发出通知。这种机制基于Object类的wait()notify()notifyAll()方法。线程可以调用wait()方法来等待,当其他线程调用了同一个对象的notify()notifyAll()方法时,正在等待的线程将被唤醒。

2.1 示例:生产者-消费者问题

生产者-消费者问题是一个经典的并发问题,它描述了一个共享固定大小的缓冲区的问题。生产者将物品放入缓冲区,消费者从缓冲区取出物品。如果缓冲区已满,生产者应该等待,直到消费者取出一些物品。同样,如果缓冲区为空,消费者应该等待,直到生产者放入一些物品。

以下是一个使用等待/通知机制解决生产者-消费者问题的示例:

public class ProducerConsumerExample {private static final int MAX_BUFFER_SIZE = 10;private int buffer = 0;public synchronized void produce() throws InterruptedException {while (buffer >= MAX_BUFFER_SIZE) {System.out.println("Buffer is full. Producer is waiting.");wait();}buffer++;System.out.println("Produced one item. Total items in buffer: " + buffer);notifyAll();}public synchronized void consume() throws InterruptedException {while (buffer <= 0) {System.out.println("Buffer is empty. Consumer is waiting.");wait();}buffer--;System.out.println("Consumed one item. Total items in buffer: " + buffer);notifyAll();}
}

在这个例子中,produce()consume()方法会分别在缓冲区满和空时进行等待,等待其他线程调用notifyAll()方法来唤醒它们。synchronized关键字确保了每次只有一个线程可以进入同步代码块,避免了并发访问导致的数据竞态条件。

在Java中,还有另一种机制可以实现线程间的通信,那就是java.util.concurrent包中的BlockingQueue接口。BlockingQueue是一个线程安全的队列,它支持在尝试添加或移除元素时等待的操作,以及在尝试移除元素时等待直到有一个元素可供移除,或者等待直到有空间可供添加元素。

使用BlockingQueue可以使代码更简洁,也更易于理解。以下是使用BlockingQueue实现生产者-消费者模式的代码示例:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;public class ProducerConsumerWithBlockingQueueExample {private BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);public void produce() throws InterruptedException {for (int i = 0; i < 20; i++) {try {queue.put(i);System.out.println("Produced: " + i);} catch (InterruptedException e) {e.printStackTrace();}}}public void consume() throws InterruptedException {while (true) {try {int item = queue.take();System.out.println("Consumed: " + item);} catch (InterruptedException e) {e.printStackTrace();}}}
}

在这个例子中,生产者和消费者分别将产品和消费的物品放入和取出队列。由于BlockingQueue是线程安全的,因此我们不需要显式地使用synchronized关键字。当队列为空时,消费者会等待直到有新的物品被放入队列;当队列满时,生产者会等待直到有空间可以放入新的物品。

这就是Java中线程间通信的两种主要方式:通过共享内存和通过等待/通知机制。使用哪种方式取决于你的具体需求和场景。如果你需要更低级别的控制,或者需要更精细的同步操作,那么你可能需要使用synchronized关键字或者wait()/notify()方法;如果你需要更简单,更易于理解的代码,那么你可能想使用BlockingQueue接口。

3. 锁


Java的内置线程模型还提供了锁机制,这可以用于控制多个线程对共享资源的访问。通过使用synchronized关键字和相关的锁机制,我们可以确保在任何给定时间,只有一个线程可以访问特定资源。这可以防止数据竞争和不一致。

3.1 示例:使用锁实现线程安全计数器

下面的示例显示了如何使用锁来创建一个线程安全的计数器:

import java.util.concurrent.atomic.AtomicInteger;public class ThreadSafeCounter {private AtomicInteger counter = new AtomicInteger(0);public synchronized void increment() {counter.incrementAndGet();}public synchronized int getCount() {return counter.get();}
}

在这个示例中,我们使用了AtomicInteger类,它是Java中线程安全的原子类之一。此外,我们还为increment()getCount()方法添加了synchronized关键字,以确保在多线程环境中,只有一个线程可以同时执行这些方法。

4. Java并发库中的高级功能


Java的并发库提供了许多高级功能,如条件变量、倒计时门闩、循环栅栏等,这些都可以用于实现更复杂的线程间通信和同步。这些功能通常在处理更复杂的并发问题时非常有用。

4.1 示例:使用条件变量实现线程同步

下面的示例显示了如何使用条件变量实现线程同步:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Example {private Lock lock = new ReentrantLock();private Condition condition = lock.newCondition();private int value = 0;public void increment() {lock.lock();try {while (value == 0) {condition.await();  // 等待,直到value != 0}value++;System.out.println("Value: " + value);condition.signalAll();  // 通知所有等待的线程value已经改变} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public void decrement() {lock.lock();try {while (value != 0) {condition.await();  // 等待,直到value == 0}value--;System.out.println("Value: " + value);condition.signalAll();  // 通知所有等待的线程value已经改变} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}
}

在这个示例中,我们使用了条件变量Condition来控制increment()decrement()方法中的线程等待和通知。当value为0时,增加线程会等待,直到有线程调用了decrement()方法使value不为0。同样地,当value不为0时,减少线程会等待,直到有线程调用了increment()方法使value为0。通过这种方式,我们实现了线程间的同步。

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

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

相关文章

vue之组件动态添加style样式的四种写法(齐全)

题记&#xff1a; 根据情况来更改样式的情况非常普遍&#xff0c;下边就是最全的使用锦集&#xff01; 一&#xff1a; 对象 <div :style"{ color: activeColor, fontSize: fontSize px }"></div><div :style"{color:(index0?worldColor:#000…

嵌入式Linux裸机开发(五)中断管理

系列文章目录 文章目录 系列文章目录前言STM32 中断系统IMX6U中断控制8个中断GIC中断控制器GIC介绍中断IDGIC逻辑分块GIC协处理器 中断使能中断优先级 重点代码分析官方SDK函数start.S文件自行编写中断驱动文件 前言 最近在学习中发现&#xff0c;学Linux嵌入式不仅是对Linux的…

记录nacos2.0+使用nginx代理出现的问题

一、问题背景&#xff1a; 在同一台服务器&#xff08;centos7&#xff09;搭建nacos服务集群。部署了3个nacos服务&#xff0c;使用不同的端口8848,8858,8868。 使用nginx代理端口8847映射到nacos端口&#xff0c;如下 upstream nacoscluster {server 127.0.0.1:8848;server…

为Yolov7环境安装Cuba匹配的Pytorch

1. 查看Cuba版本 方法一 nvidia-smi 找到CUDA Version 方法二 Nvidia Control Panel > 系统信息 > 组件 > 2. 安装Cuba匹配版本的PyTorch https://pytorch.org/get-started/locally/这里使用conda安装 conda install pytorch torchvision torchaudio pytorch-cu…

JDK、JRE、JVM三者之间的关系

1.JDK 基本介绍 1) JDK 的全称 (Java Development Kit Java 开发工具包 ) JDK JRE java 的开发工具 [java, javac,javadoc,javap 等 ] 2) JDK 是提供给 Java 开发人员使用的&#xff0c;其中包含了 java 的开发工具&#xff0c;也包括了 JRE 。所以安装了 JDK &#xff0c;就…

python:xlwings 操作 Excel 加入图片

pip install xlwings ; xlwings-0.28.5-cp37-cp37m-win_amd64.whl (1.6 MB) 摘要&#xff1a;Make Excel fly: Interact with Excel from Python and vice versa. Requires: pywin32 编写 xlwings_test.py 如下 # -*- coding: utf-8 -*- """ xlwings 结合 …

论文研读|Protecting Intellectual Property of Deep Neural Networks with Watermarking

目录 论文信息文章简介研究动机研究方法水印生成水印嵌入版权验证 实验结果有效性&#xff08;Effectiveness&#xff09;高效性&#xff08;Converge Speed&#xff09;保真度&#xff08;Functionality&#xff09;鲁棒性&#xff08;Robustness&#xff09;Anti-剪枝攻击&am…

网络资料搬运(2)

添加链接描述(1) Ubuntu 22.04&#xff1a; 为 Ubuntu22.04 系统添加中文输入法 linux解压gz文件的命令 Ubuntu20.04出现Unit ssh.service could not be found 详解使用SSH远程连接Ubuntu服务器系统 Configuring networks&#xff08;配置网络&#xff09; 如何解压缩 tar.xz 文…

SpringBoot源码分析-自动装配-实现原理

文章目录 SpringBoot自动装配前言介绍实现原理SpringBootApplicationEnableAutoConfigurationselectImports方法没有走&#xff1f;DeferredImportSelector源码分析设计目的 总结 SpringBoot自动装配 前言 什么是自动装配&#xff1f;用过Spring的应该都知道&#xff0c;虽然…

acwing.893. 集合-Nim游戏(博弈论sg函数模板)

给定 n&#xfffd; 堆石子以及一个由 k&#xfffd; 个不同正整数构成的数字集合 S&#xfffd;。 现在有两位玩家轮流操作&#xff0c;每次操作可以从任意一堆石子中拿取石子&#xff0c;每次拿取的石子数量必须包含于集合 S&#xfffd;&#xff0c;最后无法进行操作的人视…

MySQL总结练习题

目录 1.准备数据表 2.表之间的关系 3.题目 3.1 取得每个部门最高薪水的人员名称 3.2 哪些人的薪水在部门的平均薪水之上 3.3 取得部门中&#xff08;所有人的&#xff09;平均的薪水等级 3.4 不准用组函数&#xff08;Max &#xff09;&#xff0c;取得最高薪水 3.5 取…

React过渡动画

1.react-transition-group介绍 对于实现一个组件的显示与消失的过渡动画&#xff0c;可以通过原生的CSS来实现这些过渡动画&#xff0c;但是React社区为我们提供了react-transition-group库用来完成过渡动画。 # npm npm install react-transition-group --save # yarn yar…

【数据结构】归并排序和计数排序(排序的总结)

目录 一&#xff0c;归并排序的递归 二&#xff0c;归并排序的非递归 三&#xff0c;计数排序 四&#xff0c;排序算法的综合分析 一&#xff0c;归并排序的递归 基本思想&#xff1a; 归并采用的是分治思想&#xff0c;是分治法的一个经典的运用。该算法先将原数据进行拆…

回溯之 组合类问题

1、什么时候用startindex&#xff0c;什么时候不用&#xff1f; ans&#xff1a;一般在一个集合里反复操作&#xff0c;用。在多个集合里&#xff0c;不能用

BUUCTF SimpleRev

分析 该文件为64位的ELF文件&#xff0c;运行在linux平台 使用IDA64打开 进入Decry函数 输入flag和成功的提示 看看如何才能成功拿到flag 这里比较text和str2&#xff0c;text是源代码就有的 那么str2应该就是我们输入的内容 先分析text的内容是什么 进入join函数 该函数…

【算法与数据结构】--算法基础--算法设计与分析

一、贪心算法 贪心算法是一种解决优化问题的算法设计方法&#xff0c;其核心思想是在每一步选择当前状态下的最优解&#xff0c;从而希望最终达到全局最优解。下面将介绍贪心算法的原理、实现步骤&#xff0c;并提供C#和Java的实现示例。 1.1 原理&#xff1a; 贪心算法的原…

SpringBoot项目整合MybatisPlus持久层框架+Druid数据库连接池

前言 之前搭建SpringBoot项目工程&#xff0c;所使用的持久层框架不是Mybatis就是JPA&#xff0c;还没试过整合MybatisPlus框架并使用&#xff0c;原来也如此简单。在此简单记录一下在SpringBoot项目中&#xff0c;整合MybatisPlus持久层框架、Druid数据库连接池的过程。 一、…

Eclipse iceoryx(千字自传)

1 在固定时间内实现无任何限制的数据传输 在汽车automotive、机器人robotics和游戏gaming等领域,必须在系统的不同部分之间传输大量数据。使用Linux等操作系统时,必须使用进程间通信(IPC)机制传输数据。Eclipse iceoryx是一种中间件,它使用零拷贝Zero-Copy、共享内存Share…

RPA机器人的使用条件是什么,可以使用在私域运营中吗?

随着科技的发展&#xff0c;许多新型技术为我们的生活和工作带来了极大的便利。其中&#xff0c;RPA机器人作为一种自动化工具&#xff0c;正逐渐被广泛应用于各个领域。本文将探讨RPA机器人的使用条件&#xff0c;并分析是否可以在私域运营中使用。 首先&#xff0c;了解RPA机…

JAVA--一次性输入一行数

1 使用循环逐个输入&#xff08;类C&#xff09; 首先需要创建一个用于输入的Scanner对象&#xff0c;然后使用循环来连续读取输入。当需要输入的数目未知或数目不确定时&#xff0c;这是一个常见的做法。 import java.util.Scanner;public class Main {public static void m…