【高薪程序员必看】万字长文拆解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

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

相关文章
|
5月前
|
Java 大数据 Go
从混沌到秩序:Java共享内存模型如何通过显式约束驯服并发?
并发编程旨在混乱中建立秩序。本文对比Java共享内存模型与Golang消息传递模型,剖析显式同步与隐式因果的哲学差异,揭示happens-before等机制如何保障内存可见性与数据一致性,展现两大范式的深层分野。(238字)
174 4
|
5月前
|
IDE Java 编译器
java编程最基础学习
Java入门需掌握:环境搭建、基础语法、面向对象、数组集合与异常处理。通过实践编写简单程序,逐步深入学习,打牢编程基础。
341 1
|
5月前
|
缓存 安全 Java
如何理解Java中的并发?
Java并发指多任务交替执行,提升资源利用率与响应速度。通过线程实现,涉及线程安全、可见性、原子性等问题,需用synchronized、volatile、线程池及并发工具类解决,是高并发系统开发的关键基础。(238字)
328 5
|
5月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
249 6
|
5月前
|
安全 前端开发 Java
从反射到方法句柄:深入探索Java动态编程的终极解决方案
从反射到方法句柄,Java 动态编程不断演进。方法句柄以强类型、低开销、易优化的特性,解决反射性能差、类型弱、安全性低等问题,结合 `invokedynamic` 成为支撑 Lambda 与动态语言的终极方案。
249 0
|
5月前
|
Java Go 开发工具
【Java】(9)抽象类、接口、内部的运用与作用分析,枚举类型的使用
抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接 口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类abstract static不能同时修饰一个方法。
281 1
|
5月前
|
存储 Java Go
【Java】(3)8种基本数据类型的分析、数据类型转换规则、转义字符的列举
牢记类型转换规则在脑海中将编译和运行两个阶段分开,这是两个不同的阶段,不要弄混!
300 2
|
6月前
|
SQL Java 数据库
2025 年 Java 从零基础小白到编程高手的详细学习路线攻略
2025年Java学习路线涵盖基础语法、面向对象、数据库、JavaWeb、Spring全家桶、分布式、云原生与高并发技术,结合实战项目与源码分析,助力零基础学员系统掌握Java开发技能,从入门到精通,全面提升竞争力,顺利进阶编程高手。
1145 2
|
6月前
|
Java 开发者
Java并发编程:CountDownLatch实战解析
Java并发编程:CountDownLatch实战解析
548 100
|
5月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
286 1