深入理解并发编程与线程安全问题

并发编程是多线程程序设计的核心,而线程安全问题则是并发编程中的重要挑战。在现代多核处理器时代,程序员需要理解并发和并行之间的区别,并学会在并发环境下如何避免线程安全问题。本文将深入探讨并发编程中的常见线程安全问题,并提供解决方案,以帮助开发人员编写更可靠、高效的并发程序。

1. 什么是并发与并行?

在讨论线程安全问题之前,我们先来了解一下并发并行这两个概念。

  • 并发(Concurrency):是指多个任务的逻辑上同时进行。虽然这些任务可能在同一时刻只由一个处理器核心处理,但它们是交替执行的。并发关注任务的组织和调度,而不一定要求任务在物理上同时执行。

  • 并行(Parallelism):是指多个任务在物理上同时进行,通常是在多核 CPU 上运行多个任务。并行可以显著提高计算密集型任务的执行效率。

并发程序中的多个线程往往是共享资源的,因此在编写并发程序时,如何确保共享资源的安全访问是一个至关重要的问题。

2. 并发编程中的线程安全问题

2.1 竞态条件(Race Condition)

竞态条件发生在多个线程访问共享资源时,线程的执行顺序没有被正确控制,导致数据的读写操作产生冲突或错误。当两个或多个线程并发访问共享数据时,如果没有适当的同步机制,它们可能会读到不一致的数据,或覆盖其他线程的更新。

示例:

public class RaceConditionExample {private static int counter = 0;public static void increment() {counter++;  // 非原子操作}public static void main(String[] args) throws InterruptedException {Runnable task = () -> {for (int i = 0; i < 1000; i++) {increment();}};Thread t1 = new Thread(task);Thread t2 = new Thread(task);t1.start();t2.start();t1.join();t2.join();System.out.println("Final counter: " + counter);  // 期望输出 2000,但实际可能小于 2000}
}

在上述代码中,counter++ 是一个复合操作:读取 counter 的值、增加 1、写回 counter。如果两个线程同时执行这个操作,就会导致竞态条件,可能导致 counter 的最终值小于预期的 2000。

2.2 可见性问题(Visibility Issue)

线程可见性问题发生在一个线程修改了共享变量的值,但另一个线程并未立刻看到这个修改。这通常是因为每个线程可能在自己的本地缓存中保存了变量的副本,而没有及时将其更新到主内存中。

示例:

public class VisibilityExample {private static boolean flag = false;public static void main(String[] args) {Thread writer = new Thread(() -> {flag = true;});Thread reader = new Thread(() -> {while (!flag) {// 等待 flag 变为 true}System.out.println("Flag is true!");});writer.start();reader.start();}
}

在上述代码中,writer 线程将 flag 设置为 true,而 reader 线程不停检查 flag 的值。由于没有同步机制,reader 线程可能会看到旧的 flag 值,导致程序无法正常终止。

2.3 原子性问题(Atomicity Issue)

原子性指的是操作不可分割的特性,即操作要么完全执行,要么完全不执行。对于共享资源的修改,如果操作不是原子的,就会在并发执行时出现问题。例如,counter++ 操作并不是原子的,因为它包括了三个步骤:读取值、加 1 和写回值。在多个线程同时执行时,可能会导致更新结果出错。

2.4 死锁(Deadlock)

死锁是指两个或多个线程在互相等待对方释放资源,导致程序无法继续执行。死锁通常发生在多个线程持有不同资源并且互相等待对方释放锁的情况下。

示例:

public class DeadlockExample {private static final Object lock1 = new Object();private static final Object lock2 = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (lock1) {System.out.println("Thread 1: Holding lock 1...");try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }synchronized (lock2) {System.out.println("Thread 1: Holding lock 2...");}}});Thread t2 = new Thread(() -> {synchronized (lock2) {System.out.println("Thread 2: Holding lock 2...");try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }synchronized (lock1) {System.out.println("Thread 2: Holding lock 1...");}}});t1.start();t2.start();}
}

上述代码中,t1 线程持有 lock1 并等待 lock2,而 t2 线程持有 lock2 并等待 lock1,形成了死锁,导致程序无法继续执行。

3. 解决线程安全问题的常见方法

3.1 使用同步(Synchronized)

通过 synchronized 关键字,可以确保一个线程在执行临界区代码时,其他线程无法同时进入该代码块。synchronized 会为临界区加锁,确保共享资源的访问是互斥的。

示例:

public class SynchronizedExample {private static int counter = 0;public synchronized static void increment() {counter++;  // 现在是原子操作}public static void main(String[] args) throws InterruptedException {Runnable task = () -> {for (int i = 0; i < 1000; i++) {increment();}};Thread t1 = new Thread(task);Thread t2 = new Thread(task);t1.start();t2.start();t1.join();t2.join();System.out.println("Final counter: " + counter);  // 期望输出 2000}
}

在此示例中,synchronized 确保了 increment 方法在同一时刻只能被一个线程执行,从而避免了竞态条件。

3.2 使用原子类(Atomic)

对于一些简单的操作,可以使用 java.util.concurrent.atomic 包中的原子类,如 AtomicIntegerAtomicLong 等。这些类提供了原子操作,能够保证线程安全,而不需要显式加锁。

示例:

import java.util.concurrent.atomic.AtomicInteger;public class AtomicExample {private static AtomicInteger counter = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {Runnable task = () -> {for (int i = 0; i < 1000; i++) {counter.incrementAndGet();  // 原子操作}};Thread t1 = new Thread(task);Thread t2 = new Thread(task);t1.start();t2.start();t1.join();t2.join();System.out.println("Final counter: " + counter.get());  // 期望输出 2000}
}

AtomicInteger 提供了 incrementAndGet() 等原子操作方法,能够在多线程环境下安全地更新 counter 变量。

3.3 使用锁(Lock)

ReentrantLock 是一种显式锁,它可以替代 synchronized,提供了更多的灵活性和控制,如尝试加锁、可中断加锁等。

3.4 避免死锁

为了避免死锁,确保程序在获取多个锁时总是以相同的顺序获取锁,避免循环依赖。

4. 总结

在并发编程中,线程安全是一个至关重要的课题。了解并发编程中的常见线程安全问题(如竞态条件、可见性问题、原子性问题和死锁)以及如何通过同步机制、原子操作和显式锁来解决这些问题,可以帮助开发人员编写更高效、可靠的多线程程序。

理解并发背后的原理和技巧,合理使用 Java 提供的并发工具类,是保证程序性能和稳定性的关键。在多核时代,掌握并发编程是每个 Java 开发人员不可或缺的技能。

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

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

相关文章

10 JVM内置锁

我们先想明白一个问题&#xff0c;什么是锁&#xff1f; 我们去给自己家锁门的时候&#xff0c;只有对应的一把钥匙能开锁。当用钥匙去开锁的时候&#xff0c;锁孔的内置型号会验证钥匙能不能对的上。能对上就能把锁打开&#xff0c;然后进到家里使用家里的资源。否则就在外面等…

建立在商用GPT上的简单高效单细胞表示模型

大规模基因表达数据正被用于单细胞表示模型的预训练。然而&#xff0c;这样的模型需要大量的数据管理和训练。在这里&#xff0c;作者探索了一种更简单的替代方案&#xff1a;使用 GPT-3.5 从单个基因的文本描述中生成基因嵌入&#xff0c;然后通过基因表达量加权gene embeddin…

tryhackme-Pre Security-Defensive Security Intro(防御安全简介)

任务一&#xff1a;Introduction to Defensive Security防御安全简介 此room的两个要点&#xff1a; Preventing intrusions from occurring 防止入侵发生Detecting intrusions when they occur and responding properly 检测发生的入侵并正确响应 防御安全还有更多内容。 除上…

[Unity] Text文本首行缩进两个字符

Text文本首行缩进两个字符的方法比较简单。通过代码把"\u3000\u3000"加到文本字符串前面即可。 比如&#xff1a; 效果&#xff1a; 代码&#xff1a; TMPtext1.text "\u3000\u3000" "选择动作类型&#xff1a;";

Python:Matplotlib详细使用

1.Matplotlib简介 Matplotlib是python数据分析三剑客之一&#xff0c;是一个功能强大且非常流行的Python数据可视化库。Matplotlib可用于绘制折线图(line plot)、散点图(scatter plot)、条形图(bar plot)、直方图(histogram plot)、饼图(pie plot)等&#xff0c;同时也支持部分…

MIT S6081 2024 Lab 1 | Operating System | Notes

目录 安装与下载 实验1 开始我们的实验 sleep&#xff08;简单&#xff09; pingpong&#xff08;简单&#xff09; primes (中等)/(困难) find&#xff08;中等&#xff09; xargs&#xff08;中等&#xff09; finally Reference I. Tools Debian 或 Ubuntu Arch…

【Java】mac安装Java17(JDK17)

文章目录 下载java17一、安装二、环境变量 下载java17 官网下载&#xff1a;https://www.oracle.com/java/technologies/downloads 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、安装 直接安装后&#xff0c;安装完后目录会存放在下面目录下 /…

【USB-HID】“自动化键盘“

这里写目录标题 【USB-HID】"自动化键盘"1. 前言2. 框架3. 实现3.1 模拟键盘按键输入 【USB-HID】“自动化键盘” 1. 前言 最近从朋友那了解了一种"自动化键盘"&#xff0c;能够通过上位机录制按键脚本&#xff0c;然后执行脚本&#xff0c;实现物理键盘…

XXE靶机漏洞复现通关

1.扫描XXE靶机的ip地址 将kali虚拟机和XXE靶机部署在同一局域网中&#xff0c;都采用NAT网络模式 搭建好后在kali终端中进行扫描XXE靶机的ip arp-scan -l 根据常识我们可以推断192.168.27.153为靶机的ip地址 2.访问靶机页面并扫描附录 进入页面后我们可以打开御剑扫描网页中…

leetcode 36.有效的数独

1.题目要求: 2.题目步骤: 写好判断函数 3.题目代码: class Solution { public:bool isvalid(vector<vector<char>>& board,char num,int row,int col){//先找左下标int leftrow row - 1;while(leftrow > 0){if(board[leftrow][col] num){return fals…

在C#中测试比较目录的不同方法以查看它们有哪些共同的文件

C# 中的示例“比较目录以查看它们有哪些共同的文件”使用Directory.GetFiles获取两个目录中的文件。它对文件进行排序&#xff0c;并比较两个排序后的列表以查看哪些文件位于第一个目录中、第二个目录中或两个目录中。有关其工作原理的详细信息&#xff0c;请参阅该示例。 Kur…

【Java基础面试题019】什么是Java中的不可变类?

回答重点 不可变类是指在创建后无法被修改的类。一旦对象被创建&#xff0c;它的所有属性都不能被更改。这种类的实例在整个生命周期内保持不变。 关键特征&#xff1a; 声明类为final&#xff0c;防止子类继承类的所有字段都是private和final&#xff0c;确保它们在初始化后…

【论文笔记】Editing Models with Task Arithmetic

&#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为往圣继绝学&#xff0c;为万世开太平。 基本信息 标题: Editing Models with Task…

HarmonyOS(71) 自定义事件分发之TouchTestStrategy使用说明

TouchTestStrategy 1、前言2、TouchTestStrategy简介2.1、TouchTestStrategy枚举类型简介2.2、TouchTestStrategy.DEFAULT效果1.3、TouchTestStrategy.FORWARD_COMPETITION效果2.3、TouchTestStrategy.FORWARD效果3、参考资料1、前言 本文根据官方文档自定义事件分发整理而来,…

【附源码】Electron Windows桌面壁纸开发中的 CommonJS 和 ES Module 引入问题以及 Webpack 如何处理这种兼容

背景 在尝试让 ChatGPT 自动开发一个桌面壁纸更改的功能时&#xff0c;发现引入了一个 wallpaper 库&#xff0c;这个库的入口文件是 index.js&#xff0c;但是 package.json 文件下的 type:"module"&#xff0c;这样造成了无论你使用 import from 还是 require&…

WebGIS城市停水及影响范围可视化实践

目录 前言 一、相关信息介绍 1、停水信息的来源 2、停水包含的相关信息 二、功能简介 1、基础小区的整理 2、停水计划的管理 三、WebGIS空间可视化 1、使用到的组件 2、停水计划的展示 3、影响小区的展示 4、实际效果 四、总结 前言 城市停水&#xff0c;一个看似…

群落生态学研究进展】Hmsc包开展单物种和多物种分析的技术细节及Hmsc包的实际应用

联合物种分布模型&#xff08;Joint Species Distribution Modelling&#xff0c;JSDM&#xff09;在生态学领域&#xff0c;特别是群落生态学中发展最为迅速&#xff0c;它在分析和解读群落生态数据的革命性和独特视角使其受到广大国内外学者的关注。在学界不同研究团队研发出…

docker 部署 redis

docker 部署 redis 1. 下载 redis 镜像 # docker images | grep redis bitnami/redis 7.2.4-debian-11-r5 45de196aef7e 10 months ago 95.2MB2. docker-compose 部署 version: "3" services:redis:image: bitnami/redis:7.2.4-debian-11-…

Windows环境 (Ubuntu 24.04.1 LTS ) 国内镜像,用apt-get命令安装RabbitMQ,java代码样例

一、环境 Windows11 WSL(Ubuntu 24.04.1) 二、思路 1 用Windows中的Ubuntu安装RabbitMQ&#xff0c;贴近Linux的线上环境&#xff1b; 2 RabbitMQ用erlang语言编写的&#xff0c;先安装erlang的运行环境&#xff1b; 2 用Linux的apt-get命令安装&#xff0c;解决软件依赖…

Linux网络应用——高级IO

目录 1. I/O 的重新理解 2. 五种 I/O 模型 ① 阻塞 && 非阻塞 ② 同步 I/O && 异步 I/O 3. select 模型 ① 非阻塞 I/O ② select 接口介绍 ③ 代码实现 ④ select 模型的优缺点 4. poll 模型 ① poll 接口介绍 ② 代码实现 ③ 与 select 模型的…