Java 并发/多线程教程(八)-竞态条件和临界区

简介:       本系列译自jakob jenkov的Java并发多线程教程,个人觉得很有收获。由于个人水平有限,不对之处还望矫正!       竞态条件是在临界区内可能发生的一种特殊情况。

      本系列译自jakob jenkov的Java并发多线程教程,个人觉得很有收获。由于个人水平有限,不对之处还望矫正!

      竞态条件是在临界区内可能发生的一种特殊情况。临界区是多线程并发执行一代码,根据线程的执行顺序可能产生多种结果的区域。多线程在临界区执行代码的结果可能不一样,不同的结果取决于线程的执行顺序。也就是说,临界区包含竞态条件。竞态一词源于隐喻,线程在临界区进进行资源竞争,在临界区的资源竞争影响最后的结果。

     这听起来有点复杂,在下面的篇幅中,将会在下面的篇幅中介绍更多与竞态条件和临界区的相关内空。

临界区

     在一个应用的内部执行多个线程本身不会导致什么问题,当多个线程同时访问相同的资源,问题就出现了。举个例子:相同的内存资源(变量、数组、或者对象),系统资源(数据库、webservice)或者文件。

      实事上,这种问题仅出现在多个线程同时对这些资源进行写操作时。只要资源不更改,多个线程同时对相同的资源进行读是安全的。

      下面的这个例子当多个线程同时执行时可能会出错:

public class Counter{

    protected long count= 0;    

    public void add(long value){

         this.count=this.count+value;

   }

}

假设有两个线程A和B,在Counter的同一个实例上同时执行add方法,我们没办法预知操作系统在这两个线之间的调度顺序。这段代码在JVM中不是以原子操作的方式执行。而是把他们当作一组指令去执行:

1、从内存中读取this.count的值到寄存器

2、把值添添加后写到寄存器

3、把寄存器中的值写回到内存中

观察线程A和线程B的执行,将会发生些什么。

this.count = 0;

A: Reads this.count into register(0);

B:Reads this.count into register(0);

B:Add value 2 to register;

B:Writes register value(2) back to memory, this.count now equals 2

A:Add value 3 to register;

A:Writes register value(3) back to memory,this.count now equals 3

      这两个线程的目的是想对count进行加2,加3操作。因此这两个线程的执行结果预期应该为5,然而,由于这两个线程的交错执行,结果将会不同。 在上面的例子中,A线程和B线程刚开始从内存中读取到的数据都是0,然后它们各自将值加到counter上,然后将值写回到内存中,而不是5,this.count的值的最后的值就是最后一个写这个值的线程,在上面的例子中,他可能是A线程,也有可能会是B线程。

临界区的竞态条件

       在上面的例子中,当执行add()方法时,当多线程去执行这段代码时,在临界区就产生了竞态条件。

      当两个线程访问相同的资源,他们的访问顺序就叫做竞态条件,导致竞态条件产生的代码区就是临界区。

预防竞态条件

        要预防竞态条件的产生,你要确保临界区的代码以原子指令执行。也就是说一次只有单一的线程去执行它,在这个线程执行完,离开临界区之前,没有其他的线程可以执行。

      竞态条件也可以通过临界区的线程同步来避免。线程同步可以采用java同步代码块、锁或者原子变量(java.util.concurrent.atomic.AtomicInteger)来实现 。

临界区吞吐量

对于较小的临界区,使得整个临界区一起同步工作。但是,较大的临界区分解成较小的临界区有可能会有好处,它允许多个线程在较小的临界区中执行,这样可以减少共享资源的竞争,提升整个临界区的吞吐量。

下面由简单的例子来说明,我想要表达的意思:

public class TwoSums{

    private int sum1 = 0;

    private int sum2 = 0;

    public void add(int val1,int val2){

         synchronized(this){

               this.sum1 +=val1;

               this.sum2 +=val2;

          }

     }

}

注意,这个add()方法把相加后的值赋给两个不同的变量。为了避免竞态条件,求和的代码在java的同步代码块中执行。通过这种方式,在同时执行这段代码时,只有一个线程执行求和操作。然而,由于两个求和参数是两个完全独立的变量,你可以把他们分散到两个同步代码块中去求和。就像下面一样:

public class TwoSums{

   private int sum1 = 0;

   private int sum2 = 0;

   private Integer sum1Lock  = new Integer(1);

   private Integer sum2Lock = new Integer(2);

   private void add(int val1,int val2){

         synchronized(this.sum1Lock){

               this.sum1 += val1;

         }

         synchronized(this.sum2Lock){

               this.sum2 += val2;

          }

   }

}

现在两个线程可以同时执行add()方法。其中一个线程进行第一个同步代码块,第二线程进入到第二个同步代码块,由于这两个同步代码对不同的对象进行同步操作,所以两个不同的线程可以各自独立的去执行这两个代码块。通过这种方式,线程可以尽量少的减少等待去执行add()方法。

这个例子非常简单,当然,在实际的运用中,共享资源可能分解可能更为复杂,需要更多的去分析他们执行顺序的可能性。

目录
相关文章
|
6月前
|
Java 关系型数据库 数据库
Java 项目实战教程从基础到进阶实战案例分析详解
本文介绍了多个Java项目实战案例,涵盖企业级管理系统、电商平台、在线书店及新手小项目,结合Spring Boot、Spring Cloud、MyBatis等主流技术,通过实际应用场景帮助开发者掌握Java项目开发的核心技能,适合从基础到进阶的学习与实践。
993 3
|
7月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
343 0
|
5月前
|
安全 Java
Java之泛型使用教程
Java之泛型使用教程
415 10
|
4月前
|
Oracle Java 关系型数据库
Java 简单教程
Java是跨平台、面向对象的编程语言,广泛用于企业开发、Android应用等。本教程涵盖环境搭建、基础语法、流程控制、面向对象、集合与异常处理,助你快速入门并编写简单程序,为进一步深入学习打下坚实基础。
420 0
|
JavaScript NoSQL Java
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
614 96
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
|
7月前
|
缓存 安全 Java
Java 并发新特性实战教程之核心特性详解与项目实战
本教程深入解析Java 8至Java 19并发编程新特性,涵盖CompletableFuture异步编程、StampedLock读写锁、Flow API响应式流、VarHandle内存访问及结构化并发等核心技术。结合电商订单处理、缓存系统、实时数据流、高性能计数器与用户资料聚合等实战案例,帮助开发者高效构建高并发、低延迟、易维护的Java应用。适合中高级Java开发者提升并发编程能力。
295 0
|
8月前
|
Java 数据挖掘 调度
Java 多线程创建零基础入门新手指南:从零开始全面学习多线程创建方法
本文从零基础角度出发,深入浅出地讲解Java多线程的创建方式。内容涵盖继承`Thread`类、实现`Runnable`接口、使用`Callable`和`Future`接口以及线程池的创建与管理等核心知识点。通过代码示例与应用场景分析,帮助读者理解每种方式的特点及适用场景,理论结合实践,轻松掌握Java多线程编程 essentials。
604 5
|
8月前
|
监控 搜索推荐 Java
Java 多线程最新实操技术与应用场景全解析:从基础到进阶
本文深入探讨了Java多线程的现代并发编程技术,涵盖Java 8+新特性,如CompletableFuture异步处理、Stream并行流操作,以及Reactive编程中的Reactor框架。通过具体代码示例,讲解了异步任务组合、并行流优化及响应式编程的核心概念(Flux与Mono)。同时对比了同步、CompletableFuture和Reactor三种实现方式的性能,并总结了最佳实践,帮助开发者构建高效、扩展性强的应用。资源地址:[点击下载](https://pan.quark.cn/s/14fcf913bae6)。
490 3
|
8月前
|
Oracle Java 关系型数据库
java 编程基础入门级超级完整版教程详解
这份文档是针对Java编程入门学习者的超级完整版教程,涵盖了从环境搭建到实际项目应用的全方位内容。首先介绍了Java的基本概念与开发环境配置方法,随后深入讲解了基础语法、控制流程、面向对象编程的核心思想,并配以具体代码示例。接着探讨了常用类库与API的应用,如字符串操作、集合框架及文件处理等。最后通过一个学生成绩管理系统的实例,帮助读者将理论知识应用于实践。此外,还提供了进阶学习建议,引导学员逐步掌握更复杂的Java技术。适合初学者系统性学习Java编程。资源地址:[点击访问](https://pan.quark.cn/s/14fcf913bae6)。
1060 2
|
9月前
|
算法 Java 调度
Java多线程基础
本文主要讲解多线程相关知识,分为两部分。第一部分涵盖多线程概念(并发与并行、进程与线程)、Java程序运行原理(JVM启动多线程特性)、实现多线程的两种方式(继承Thread类与实现Runnable接口)及其区别。第二部分涉及线程同步(同步锁的应用场景与代码示例)及线程间通信(wait()与notify()方法的使用)。通过多个Demo代码实例,深入浅出地解析多线程的核心知识点,帮助读者掌握其实现与应用技巧。
161 1

热门文章

最新文章