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()方法。

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

目录
相关文章
|
9天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
7天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
7天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
6天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
12天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
37 9
|
9天前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。
|
12天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
8天前
|
Java 编译器 Android开发
Kotlin教程笔记(28) -Kotlin 与 Java 混编
Kotlin教程笔记(28) -Kotlin 与 Java 混编
10 0
|
3月前
|
安全 Java 调度
解锁Java并发编程高阶技能:深入剖析无锁CAS机制、揭秘魔法类Unsafe、精通原子包Atomic,打造高效并发应用
【8月更文挑战第4天】在Java并发编程中,无锁编程以高性能和低延迟应对高并发挑战。核心在于无锁CAS(Compare-And-Swap)机制,它基于硬件支持,确保原子性更新;Unsafe类提供底层内存操作,实现CAS;原子包java.util.concurrent.atomic封装了CAS操作,简化并发编程。通过`AtomicInteger`示例,展现了线程安全的自增操作,突显了这些技术在构建高效并发程序中的关键作用。
70 1
|
19天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
下一篇
无影云桌面