【高薪程序员必看】万字长文拆解Java并发编程!(3-1):并发共享问题的解决与分析

简介: 活锁:多个线程相互影响对方退出同步代码块的条件而导致线程一直运行的情况。例如,线程1的退出条件是count=5,而线程2和线程3在其代码块中不断地是count进行自增自减的操作,导致线程1永远运行。内存一致性问题:由于JIT即时编译器对缓存的优化和指令重排等造成的内存可见性和有序性问题,可以通过synchronized,volatile,并发集合类等机制来解决。这里的线程安全是指,多个线程调用它们同一个实例的方法时,是线程安全的,但仅仅能保证当前调用的方法是线程安全的,不同方法之间是线程不安全的。

目录


3. 并发共享

3.1. 并发问题

3.1.1. 临界区

3.1.2. 竞态条件

3.1.3. 死锁

3.1.4. 活锁

3.1.5. 饥饿

3.1.6. 内存一致性

3.2. 线程共享问题解决

3.3. 变量的线程安全分析

3.3.1. 成员变量和静态变量

3.3.2. 局部变量

3.4. 常见线程安全类



3. 并发共享

3.1. 并发问题

多个线程同时对共享资源进行修改时,有可能会发生读写操作的指令交错,导致结果跟预期不一样

3.1.1. 临界区

临界区(critical region):一段对共享资源的多线程并发读写操作的代码块

3.1.2. 竞态条件

竞态条件(Race Condition):多个线程代码在临界区执行,由于执行顺序不同而导致结果无法预测,例如数据丢失不一致等

解决方法:使用锁,同步工具,原子类等机制

3.1.3. 死锁

死锁的概念:多个线程由于资源竞争或通信问题导致的阻塞情况。例如:线程1持有线程2所需的资源,且线程1不释放,那么线程2就永远阻塞。

死锁的预防:

  1. 破坏互斥条件,允许资源共享
  2. 请求不到资源时,释放自己持有的资源
  3. 按序获取资源,避免循环等待

死锁的解决:

  1. 杀死持有资源的进程
  2. 操作系统强制释放资源
  3. 回滚进程

3.1.4. 活锁

活锁:多个线程相互影响对方退出同步代码块的条件而导致线程一直运行的情况。例如,线程1的退出条件是count=5,而线程2和线程3在其代码块中不断地是count进行自增自减的操作,导致线程1永远运行。

活锁的解决:

  1. 引入随机性,在检测到活锁时,暂停随机时间再重新尝试
  2. 设置重试次数,到达一定次数强制退出
  3. 活锁发生时主动干预,打破活锁

3.1.5. 饥饿

饥饿(Starvation):某个线程长期无法获取CPU时间片执行权而无法运行的情况,一般发生在字段调用不公平或优先级设置有问题的情况下

3.1.6. 内存一致性

内存一致性问题:由于JIT即时编译器对缓存的优化和指令重排等造成的内存可见性和有序性问题,可以通过synchronized,volatile,并发集合类等机制来解决

3.2. 线程共享问题解决

有多种方法可以避免临界区中的竞态条件:

  • 阻塞式-悲观锁:synchronized,Lock
  • 非阻塞式-乐观锁:原子变量

3.3. 变量的线程安全分析

3.3.1. 成员变量和静态变量

成员变量和静态变量的线程安全分析要分两种情况:

  • 不被共享时是线程安全的
  • 被多个线程共享时,分为两种情况
  • 读操作:是线程安全的
  • 写操作:是线程不安全的

3.3.2. 局部变量

局部变量是否线程安全在于它是否逃逸出方法的作用范围

  • 如果局部变量作用范围仅仅在当前方法,是线程安全的
  • 如果局部变量作为参数传入或作为返回值返回,说明局部变量没有逃逸出方法的作用范围,是线程不安全的
  • 局部变量仅有一个线程访问的时候是线程安全的
  • private和final关键字是可以提供线程安全的,因为可以防止子类重写父类方法,其中可以开启另外一个线程访问局部变量造成线程不安全

3.4. 常见线程安全类

不可变类:内部属性是不可改变的,只能读不能写,所以是线程安全的

  • String:
  • String用final关键字修饰是为了防止子类去重写String方法导致线程不安全
  • 其中的replace,substring改变了值,本质上是通过创建一个新的String对象来完成的,线程安全
  • Integer

方法加上了synchronized方法的类:

  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • java.util.concurrent包下的类,也称JUC

这里的线程安全是指,多个线程调用它们同一个实例的方法时,是线程安全的,但仅仅能保证当前调用的方法是线程安全的,不同方法之间是线程不安全的

相关文章
|
6月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
271 6
|
6月前
|
IDE Java 编译器
java编程最基础学习
Java入门需掌握:环境搭建、基础语法、面向对象、数组集合与异常处理。通过实践编写简单程序,逐步深入学习,打牢编程基础。
366 1
|
6月前
|
安全 前端开发 Java
从反射到方法句柄:深入探索Java动态编程的终极解决方案
从反射到方法句柄,Java 动态编程不断演进。方法句柄以强类型、低开销、易优化的特性,解决反射性能差、类型弱、安全性低等问题,结合 `invokedynamic` 成为支撑 Lambda 与动态语言的终极方案。
272 0
|
6月前
|
Java Go 开发工具
【Java】(9)抽象类、接口、内部的运用与作用分析,枚举类型的使用
抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接 口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类abstract static不能同时修饰一个方法。
299 0
|
10月前
|
Java 数据库连接 API
2025 更新必看:Java 编程基础入门级超级完整版指南
本教程为2025更新版Java编程基础入门指南,涵盖开发环境搭建(SDKMAN!管理JDK、VS Code配置)、Java 17+新特性(文本块、Switch表达式增强、Record类)、面向对象编程(接口默认方法、抽象类与模板方法)、集合框架深度应用(Stream API高级操作、并发集合)、模式匹配与密封类等。还包括学生成绩管理系统实战项目,涉及Maven构建、Lombok简化代码、JDBC数据库操作及JavaFX界面开发。同时提供JUnit测试、日志框架使用技巧及进阶学习资源推荐,助你掌握Java核心技术并迈向高级开发。
908 5
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
182 1
|
Java 数据处理 开发者
Java多线程编程的艺术:从入门到精通####
【10月更文挑战第21天】 本文将深入探讨Java多线程编程的核心概念,通过生动实例和实用技巧,引导读者从基础认知迈向高效并发编程的殿堂。我们将一起揭开线程管理的神秘面纱,掌握同步机制的精髓,并学习如何在实际项目中灵活运用这些知识,以提升应用性能与响应速度。 ####
177 3
Java中的多线程编程:从入门到精通
本文将带你深入了解Java中的多线程编程。我们将从基础概念开始,逐步深入探讨线程的创建、启动、同步和通信等关键知识点。通过阅读本文,你将能够掌握Java多线程编程的基本技能,为进一步学习和应用打下坚实的基础。
|
算法 Java 开发者
Java 编程入门:从零到一的旅程
本文将带领读者开启Java编程之旅,从最基础的语法入手,逐步深入到面向对象的核心概念。通过实例代码演示,我们将一起探索如何定义类和对象、实现继承与多态,并解决常见的编程挑战。无论你是编程新手还是希望巩固基础的开发者,这篇文章都将为你提供有价值的指导和灵感。