理解线程安全:保护你的代码免受并发问题困扰

2024-06-12 01:36

本文主要是介绍理解线程安全:保护你的代码免受并发问题困扰,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

前言

一、什么是线程安全?

二、为什么需要线程安全?

三、实现线程安全的方法

四、synchronized

使用 synchronized 关键字时,需要注意以下几点:

五、Demo讲解


前言

        在现代软件开发中,尤其是在多线程编程中,线程安全(Thread Safety)是一个至关重要但又复杂的话题。本文将从基础概念开始,逐步深入,帮助你理解什么是线程安全,以及如何在实际开发中实现线程安全。

一、什么是线程安全?

        线程安全是指多个线程可以同时访问和修改共享数据而不导致数据的不一致性或程序错误。简单来说,当一个对象或函数在多线程环境下能够正确地执行,并且不会引起任何未定义的行为时,它就是线程安全的。

二、为什么需要线程安全?

        多线程编程的主要目的是为了提高程序性能和响应速度。然而,当多个线程同时操作共享资源时,可能会出现竞争条件(Race Condition)、死锁(Deadlock)以及饥饿(Starvation)等问题。这些问题不仅会导致程序运行结果不正确,还可能引发崩溃和难以调试的错误。

三、实现线程安全的方法

  1. 互斥同步:使用锁(如 synchronized、ReentrantLock 等)来保护共享数据,在同一时刻只允许一个线程访问共享数据,其他线程需要等待锁释放后才能访问。

  2. 原子操作:使用原子操作来确保对共享数据的操作是不可分割的,不会被中断,常见的原子操作包括 atomic 包下的原子类,以及 volatile 关键字修饰的变量。

  3. 无锁并发编程:通过使用无锁的数据结构和算法来实现线程安全,例如 CAS(Compare and Swap)操作,乐观锁机制等。

  4. 线程封闭:将共享数据限制在单个线程内部,避免多个线程之间直接访问共享数据,从而避免竞态条件。

  5. 不可变对象:通过创建不可变对象来避免多线程并发修改共享状态,从而避免线程安全问题。

四、synchronized

        当一个线程访问一个被 synchronized 修饰的方法或代码块时,会自动获取该方法或代码块对应的锁,其他线程必须等待该线程释放锁后才能获取锁并进入临界区。这种方式可以有效避免多个线程同时访问共享数据时发生竞态条件和其他线程安全问题。

使用 synchronized 关键字时,需要注意以下几点:

  1. 修饰方法:在修饰方法时,锁对象默认为当前对象(this),不同的线程需要获取的是同一个对象的锁。

  2. 修饰代码块:在修饰代码块时,需要指定锁对象,一般可以使用 this、类名.class 等作为锁对象。需要注意的是,锁对象必须是同一个对象才能保证线程同步。

  3. 可重入性:在同一个线程中,如果一个方法已经获取了锁,那么在调用另一个被 synchronized 修饰的方法时,该线程仍然能够获取到同一个锁,即 synchronized 具有可重入性。

  4. 锁的释放:在 synchronized 块执行完毕或者抛出异常时,锁会自动释放。如果在 synchronized 块中使用了 wait() 方法,则锁也会被释放,等待其他线程唤醒后再次获取锁。

五、Demo讲解

package com.ctb.demo;/*** 线程安全概念:当多个线程访问某一个类(对象或方法)时,这个对象始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的* synchronized:可以在任意对象或方法上加锁,而加锁的这段代码称为"互斥区"或"临界区"* @author 彪**/
public class MyThread extends Thread{private int count = 5;public void run() {count--;System.out.println(this.currentThread().getName()+"count = "+count);}public static void main(String[] args) {/*** 	当多个线程访问MyThread的run方法时,以排队的方式进行处理(这里排队是按照CPU分配的先后顺序而定的)*   一个线程想要执行synchronized修饰的方法里的代码*   1.尝试获得锁*   2.如果拿到锁,执行synchronized代码内容:拿不到锁,这个线程就会不断尝试获得这把锁,直道拿到为止,*   	而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题)*/MyThread myThread = new MyThread();Thread t1 = new Thread(myThread,"t1");Thread t2 = new Thread(myThread,"t2");Thread t3 = new Thread(myThread,"t3");Thread t4 = new Thread(myThread,"t4");Thread t5 = new Thread(myThread,"t5");t1.start();t2.start();t3.start();t4.start();t5.start();}}

结果:并不是我们想要的,count--

package com.ctb.demo;/*** 线程安全概念:当多个线程访问某一个类(对象或方法)时,这个对象始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的* synchronized:可以在任意对象或方法上加锁,而加锁的这段代码称为"互斥区"或"临界区"* @author 彪**/
public class MyThread extends Thread{private int count = 5;//synchronized加锁public synchronized void run() {count--;System.out.println(this.currentThread().getName()+"count = "+count);}public static void main(String[] args) {/*** 	当多个线程访问MyThread的run方法时,以排队的方式进行处理(这里排队是按照CPU分配的先后顺序而定的)*   一个线程想要执行synchronized修饰的方法里的代码*   1.尝试获得锁*   2.如果拿到锁,执行synchronized代码内容:拿不到锁,这个线程就会不断尝试获得这把锁,直道拿到为止,*   	而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题)*/MyThread myThread = new MyThread();Thread t1 = new Thread(myThread,"t1");Thread t2 = new Thread(myThread,"t2");Thread t3 = new Thread(myThread,"t3");Thread t4 = new Thread(myThread,"t4");Thread t5 = new Thread(myThread,"t5");t1.start();t2.start();t3.start();t4.start();t5.start();}}

结果:

注意:

当多个线程访问MyThread的run方法时,以排队的方式进行处理(这里排队是按照CPU分配的先后顺序而定的) ​ 一个线程想要执行synchronized修饰的方法里的代码 ​ 1.尝试获得锁 ​ 2.如果拿到锁,执行synchronized代码内容:拿不到锁,这个线程就会不断尝试获得这把锁,直道拿到为止, ​ 而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题:前面线程count不是按顺序的)

这篇关于理解线程安全:保护你的代码免受并发问题困扰的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1052828

相关文章

Vue3绑定props默认值问题

《Vue3绑定props默认值问题》使用Vue3的defineProps配合TypeScript的interface定义props类型,并通过withDefaults设置默认值,使组件能安全访问传入的... 目录前言步骤步骤1:使用 defineProps 定义 Props步骤2:设置默认值总结前言使用T

Java中如何正确的停掉线程

《Java中如何正确的停掉线程》Java通过interrupt()通知线程停止而非强制,确保线程自主处理中断,避免数据损坏,线程池的shutdown()等待任务完成,shutdownNow()强制中断... 目录为什么不强制停止为什么 Java 不提供强制停止线程的能力呢?如何用interrupt停止线程s

Web服务器-Nginx-高并发问题

《Web服务器-Nginx-高并发问题》Nginx通过事件驱动、I/O多路复用和异步非阻塞技术高效处理高并发,结合动静分离和限流策略,提升性能与稳定性... 目录前言一、架构1. 原生多进程架构2. 事件驱动模型3. IO多路复用4. 异步非阻塞 I/O5. Nginx高并发配置实战二、动静分离1. 职责2

Redis实现高效内存管理的示例代码

《Redis实现高效内存管理的示例代码》Redis内存管理是其核心功能之一,为了高效地利用内存,Redis采用了多种技术和策略,如优化的数据结构、内存分配策略、内存回收、数据压缩等,下面就来详细的介绍... 目录1. 内存分配策略jemalloc 的使用2. 数据压缩和编码ziplist示例代码3. 优化的

解决升级JDK报错:module java.base does not“opens java.lang.reflect“to unnamed module问题

《解决升级JDK报错:modulejava.basedoesnot“opensjava.lang.reflect“tounnamedmodule问题》SpringBoot启动错误源于Jav... 目录问题描述原因分析解决方案总结问题描述启动sprintboot时报以下错误原因分析编程异js常是由Ja

Python 基于http.server模块实现简单http服务的代码举例

《Python基于http.server模块实现简单http服务的代码举例》Pythonhttp.server模块通过继承BaseHTTPRequestHandler处理HTTP请求,使用Threa... 目录测试环境代码实现相关介绍模块简介类及相关函数简介参考链接测试环境win11专业版python

Python从Word文档中提取图片并生成PPT的操作代码

《Python从Word文档中提取图片并生成PPT的操作代码》在日常办公场景中,我们经常需要从Word文档中提取图片,并将这些图片整理到PowerPoint幻灯片中,手动完成这一任务既耗时又容易出错,... 目录引言背景与需求解决方案概述代码解析代码核心逻辑说明总结引言在日常办公场景中,我们经常需要从 W

使用Spring Cache本地缓存示例代码

《使用SpringCache本地缓存示例代码》缓存是提高应用程序性能的重要手段,通过将频繁访问的数据存储在内存中,可以减少数据库访问次数,从而加速数据读取,:本文主要介绍使用SpringCac... 目录一、Spring Cache简介核心特点:二、基础配置1. 添加依赖2. 启用缓存3. 缓存配置方案方案

MySQL 表空却 ibd 文件过大的问题及解决方法

《MySQL表空却ibd文件过大的问题及解决方法》本文给大家介绍MySQL表空却ibd文件过大的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录一、问题背景:表空却 “吃满” 磁盘的怪事二、问题复现:一步步编程还原异常场景1. 准备测试源表与数据

python 线程池顺序执行的方法实现

《python线程池顺序执行的方法实现》在Python中,线程池默认是并发执行任务的,但若需要实现任务的顺序执行,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋... 目录方案一:强制单线程(伪顺序执行)方案二:按提交顺序获取结果方案三:任务间依赖控制方案四:队列顺序消