并发编程基础知识篇--并发编程的优点&缺点

这篇具有很好参考价值的文章主要介绍了并发编程基础知识篇--并发编程的优点&缺点。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

并发编程的优点&缺点

为什么要使用并发编程(优点):

并发编程的缺点:

频繁的上下文切换

线程安全

易混淆的概念

阻塞与非阻塞

阻塞模型

非阻塞模型

同步与异步

同步调用

异步调用

临界区

并发与并行

上下文切换


并发编程的优点&缺点

并发编程是指在程序中同时执行多个独立的任务或操作,提高程序的性能和响应能力。

为什么要使用并发编程(优点):

  1. 提高系统性能:通过并发编程,可以充分利用多核处理器的计算能力,实现任务的并行执行,提高系统的吞吐量和响应速度。
  2. 提高代码效率:使用并发编程技术可以通过异步执行任务,减少等待时间,提高代码执行效率,提升系统的整体性能。
  3. 增强程序的可扩展性:通过并发编程,可以将任务拆分成多个小任务并并行处理,便于灵活地添加、调整或删除任务,从而实现系统的易扩展性和模块化设计。
  4. 改善用户体验:并发编程可以使程序具备更好的响应性,避免界面冻结或阻塞,提高用户交互的体验。

并发编程的缺点:

  1. 多线程编程复杂性高:并发编程需要考虑线程安全、竞态条件、死锁等问题,对开发者的要求较高,编写和调试并发代码更加困难。
  2. 容易产生 bug:由于并发编程中存在线程间的竞争条件和共享资源的访问问题,不正确的并发代码容易导致数据不一致、死锁等问题,增加了程序中出错的风险。
  3. 性能损耗:线程的创建和上下文切换会带来一定的开销,如果并发编程不恰当地使用或管理过多的线程,会导致系统资源消耗,进而影响性能。

频繁的上下文切换

上下文切换是指操作系统在执行某个任务时,由于某种原因需要切换到另一个任务,保存当前任务的上下文(即状态信息)并加载另一个任务的上下文,然后开始执行该任务。这个过程会导致一定的性能开销,因为需要保存和加载上下文信息。

在多线程编程中,频繁的上下文切换可能会导致性能下降,因为线程之间的切换需要保存和加载线程的上下文信息,这个过程是非常耗时的。因此,我们需要采取一些措施来减少上下文切换的次数,以充分发挥多线程编程的优势。

  1. 无锁并发编程:采用锁分段的思想,不同线程处理不同段的数据,减少多线程竞争情况下的上下文切换时间。
  2. CAS算法:使用原子操作(CAS)更新数据,使用乐观锁,减少不必要的锁竞争引起的上下文切换。
  3. 使用最少线程:避免创建不必要的线程,例如任务较少时避免创建过多线程,以减少大量线程处于等待状态。
  4. 协程:在单线程中实现多任务调度,并在单线程中进行任务切换。

注意:上下文切换也是一个相对耗时的操作。在《Java并发编程的艺术》一书中有一项实验表明,并发累加未必比串行累加更快。可以使用工具如Lmbench3来测量上下文切换的时长,使用vmstat来测量上下文切换的次数。

以下是一个简单的Java代码示例,演示如何使用锁分段来减少上下文切换的情况:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Main {
    private static final int SEGMENT_COUNT = 16; // 锁分段数量
    private final Lock[] segmentLocks; // 锁分段数组
    private final int[] data; // 数据数组

    public Main() {
        // 初始化锁分段数组和数据数组
        segmentLocks = new ReentrantLock[SEGMENT_COUNT];
        for (int i = 0; i < SEGMENT_COUNT; i++) {
            segmentLocks[i] = new ReentrantLock();
        }
        data = new int[SEGMENT_COUNT];
    }

    private Lock getSegmentLock(int index) {
        // 根据索引计算锁在锁分段数组中的位置
        int segmentIndex = Math.abs(index % SEGMENT_COUNT);
        return segmentLocks[segmentIndex];
    }

    /**
     * 更新数据
     *
     * @param index 索引
     * @param value 值
     */
    public void updateData(int index, int value) {
        Lock lock = getSegmentLock(index);
        lock.lock(); // 获取锁
        try {
            data[index] = value; // 更新数据
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    /**
     * 获取数据
     *
     * @param index 索引
     * @return 数据值
     */
    public int getData(int index) {
        Lock lock = getSegmentLock(index);
        lock.lock(); // 获取锁
        try {
            return data[index]; // 返回数据
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    public static void main(String[] args) {
        Main main = new Main();

        // 示例调用 updateData() 和 getData() 方法
        main.updateData(0, 1);
        int value = main.getData(0);
        System.out.println("价值: " + value);//价值: 1
    }
}

线程安全

多线程编程中最难以把握的就是临界区线程安全问题,稍微不注意就会出现死锁的情况,一旦产生死锁就会造成系统功能不可用。

为了避免死锁情况的发生,可以采取以下几种措施:

  1. 避免一个线程同时获得多个锁,避免引起死锁的风险。
  2. 每个锁只占用一个资源,避免一个线程在锁内部占有多个资源。
  3. 使用定时锁,使用tryLock()方法尝试获取锁,并在超时后释放该锁,避免线程无限等待。
  4. 对于数据库锁,确保加锁和解锁操作必须在同一个数据库连接内进行,避免解锁失败的情况。

此外,理解JVM内存模型在原子性、有序性和可见性方面的问题也非常重要。例如,数据脏读是指一个线程修改了某个共享变量的值,但尚未刷新到主内存中,另一个线程读取该变量时仍看到旧的值。DCL(Double Check Lock)是一种优化技术,用于减少锁的开销,但它可能会导致数据竞争和线程安全问题。

学习多线程编程技术需要深入理解并发编程的概念和原理,掌握各种并发工具和技术,并能够根据具体场景选择合适的解决方案。通过学习和实践,可以提升自己的并发编程能力和程序性能,同时提高对多线程编程的理解和掌握。

当涉及到临界区线程安全问题时,下面是一个使用锁机制来确保线程安全的Java代码示例: 

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Main {
    private int count = 0;
    private Lock lock = new ReentrantLock(); // 创建一个可重入锁

    public void increment() {
        lock.lock(); // 获取锁
        try {
            count++; // 临界区,对共享变量进行操作
        } finally {
            lock.unlock(); // 释放锁,确保在发生异常时也能正常释放锁
        }
    }

    public int getCount() {
        return count;
    }

    public static void main(String[] args) {
        Main example = new Main();

        // 创建多个线程并执行increment方法
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    example.increment();
                }
            });
            thread.start();
        }

        // 等待所有线程执行完毕
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 输出计数结果
        System.out.println("计数= " + example.getCount());//运行结果:计数= 5000
    }
}

易混淆的概念

阻塞与非阻塞

阻塞和非阻塞是并发编程中的重要概念,它们描述了一个线程在等待某个操作完成时的行为。

阻塞模型

在阻塞模型中,当一个线程调用某个操作后,如果该操作尚未完成,线程会被挂起,进入等待状态,直到操作完成。在这个过程中,线程不会执行其他任务,因此阻塞模型是一种忙等待(busy-waiting)的方式。在Java中,一些I/O操作(如读取文件或网络连接)是阻塞的,如果操作尚未完成,线程会被挂起,直到操作完成。

非阻塞模型

非阻塞模型则不会挂起线程。在非阻塞模型中,线程调用某个操作后,会立即返回一个结果,无论操作是否完成。这个结果可能是操作的部分完成或者是错误状态。在这个过程中,线程可以继续执行其他任务,因此非阻塞模型可以提高系统的并发性和效率。在Java中,一些并发编程的工具类如java.util.concurrent中的Lock和Semaphore都是非阻塞的,它们提供了非阻塞的线程同步和计数操作。

在实际应用中,阻塞和非阻塞模型各有优缺点。

  1. 阻塞模型简单易懂,但是可能导致系统的效率低下。
  2. 非阻塞模型则可以提高系统的并发性和效率,但是可能需要更复杂的逻辑来处理操作的结果和状态。

同步与异步

在计算机编程中,同步和异步是处理任务和数据流的两种主要方式。

同步调用

同步调用是一种阻塞调用,意味着调用者会一直等待被调用者完成操作并返回结果。在调用者等待期间,它的线程是阻塞的,不能执行其他任务。只有当被调用者完成任务并返回结果后,调用者才会继续执行后续代码。在同步模型中,调用者和被调用者之间有一个明确的请求-响应关系,调用者必须等待响应才能进行下一步操作。

异步调用

异步调用则是一种非阻塞调用,意味着调用者在发送请求后会立即返回,而不会等待被调用者完成操作。调用者可以继续执行其他任务,而不需要等待被调用者完成。异步模型中,调用者和被调用者之间没有明确的请求-响应关系,而是通过回调函数或者事件通知来传递结果。

异步调用,要想获得结果,一般有两种方式:

  1. 主动轮询异步调用的结果;
  2. 被调用方通过callback来通知调用方调用结果;

异步调用的优点在于可以提高系统的并发性和效率,因为调用者可以在等待结果的同时执行其他任务。然而,异步模型也有一些缺点,比如可能需要更复杂的逻辑来处理回调函数和结果通知,以及可能面临一些并发问题,如竞争条件和死锁等。

在实际应用中,选择同步还是异步取决于具体的需求和场景。对于需要立即获取结果的操作,通常使用同步调用;而对于不需要立即获取结果的场景,如长时间的I/O操作或网络请求,通常使用异步调用来提高系统的效率和性能。

临界区

临界区(Critical section)是一种编程中的概念,用于管理对共享资源的访问。在多线程编程中,多个线程可能同时尝试访问和修改共享数据,这可能会导致数据不一致和其他并发问题。临界区提供了一种方法来确保在任何给定时间只有一个线程可以访问共享资源,从而避免了这些问题。

临界区通常是在一段代码中定义的一个区域,其中包含了需要访问共享资源的操作。在进入临界区之前,线程必须获取一个锁,以防止其他线程同时进入临界区。当线程离开临界区时,它必须释放该锁,允许其他线程进入临界区。

临界区的使用可以确保数据的一致性和并发控制。它还可以防止数据竞争和其他并发问题,例如死锁和饥饿。然而,临界区的使用也可能导致线程之间的阻塞和等待,这可能会影响程序的性能和响应性。因此,在使用临界区时,需要权衡其优缺点,并根据实际情况做出决策。

在编程中,有许多不同的同步原语(synchronization primitives)可以实现临界区的功能,例如互斥锁(mutexes)、读写锁(read-write locks)、信号量(semaphores)等。

并发与并行

  1. 并发是指在一个时间段内,多个任务交替执行。这些任务可能部分重叠,但它们不会真正同时执行。在单核CPU的时代,并发是通过时间片轮转的方式实现的,即CPU在多个任务之间交替执行,每个任务执行一段时间,然后切换到另一个任务。这样,虽然每个任务不是真正的同时执行,但从用户的角度来看,它们似乎是同时进行的。
  2. 并行是指在同一时间内,多个任务同时执行。这需要多核CPU或多线程的环境支持。并行可以大大提高程序的执行效率,特别是在需要处理大量计算或数据的情况下。
  3. 串行是指多个任务或方法在一个线程中顺序执行。这意味着任务或方法一个接一个地执行,没有重叠的部分。串行执行在单线程环境中很常见,比如使用单一线程的程序或某些串行执行的函数或方法。

上下文切换

上下文切换是多线程编程中的一个重要概念,它是指当一个线程的时间片用完时,CPU会将该线程的状态保存起来,然后加载另一个就绪状态的线程并执行。这个过程就是一次上下文切换。

上下文切换的目的是为了让多个线程能够共享 CPU 资源,并且每个线程都能够得到一定的执行时间。由于一个 CPU 核心在任意时刻只能被一个线程使用,因此 CPU 采取轮转的方式来为每个线程分配时间片。

上下文切换的过程包括保存当前线程的状态(包括寄存器状态、内存中的变量等)和加载下一个就绪状态的线程的状态。这个过程需要一定的时间,并且随着系统中的线程数量的增加,上下文切换的次数也会增加,这会消耗大量的 CPU 时间。

在 Linux 等操作系统中,上下文切换和模式切换的时间消耗相对较少,这也是 Linux 性能优秀的一个重要原因。这是因为 Linux 采用了许多优化技术,比如使用内核调度程序、使用硬件中断等,来减少上下文切换和模式切换的时间消耗。文章来源地址https://www.toymoban.com/news/detail-679554.html

到了这里,关于并发编程基础知识篇--并发编程的优点&缺点的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 《Java SE》网络编程基础知识归纳。

    目录 一、网络基本介绍 1、什么是网络通信? 2、网络 3、IP地址 4、域名 5、网络通信协议 6、Socket 二、TCP网络通信编程  1、应用实例1(字节流) 2、应用实例2(字节流) 3、应用实例3(字符流) 4、netstat 指令 三、UDP网络通信编程  1、基本介绍 2、基本流程 3、应用实例  

    2024年01月20日
    浏览(51)
  • 【Java之家-编程的衣柜】线程的基础知识及线程与进程的联系

    线程是什么 一个线程就是一个 “执行流”. 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 “同时” 执行 着多份代码. 轻量级进程 - 线程(Thread) 为什么要有线程 首先,“并发编程”成为“刚需” 其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量

    2024年02月07日
    浏览(51)
  • Java入门高频考查算法逻辑基础知识3-编程篇(超详细18题1.8万字参考编程实现)

    准备这些面试题时,请考虑如下准备步骤: 理解问题并澄清任何可能的疑点。确保你了解了面试官的期望,包括问题限制条件和期望的解决方案。 如果可能且适用的话,尝试先给出一个简单的解决方案,比如暴力法,然后再逐步优化它。 在优化之前,先分析暴力解法的效率

    2024年01月18日
    浏览(62)
  • Java 学习路线:基础知识、数据类型、条件语句、函数、循环、异常处理、数据结构、面向对象编程、包、文件和 API

    Java 是一种由 Sun Microsystems 于 1995 年首次发布的编程语言和计算平台。Java 是一种通用的、基于类的、面向对象的编程语言,旨在减少实现依赖性。它是一个应用程序开发的计算平台。Java 快速、安全、可靠,因此在笔记本电脑、数据中心、游戏机、科学超级计算机、手机等领

    2024年03月24日
    浏览(86)
  • Python编程语言的特点(优点和缺点)

    目录 Python的优点 1) 语法简单 2) Python 是开源的 3) Python 是免费的 4) Python 是高级语言 5) Python 是解释型语言,能跨平台 6) Python 是面向对象的编程语言 7) Python 功能强大(模块众多) 8) Python 可扩展性强 Python 的缺点 1) 运行速度慢 2) 代码加密困难 Python 是一种开源的解释型脚本编

    2024年02月07日
    浏览(79)
  • vbs编程的优点和缺点:你需要知道的一切

    VBScript(Visual Basic Scripting Edition)是一种基于Visual Basic的脚本语言,它可以用于Windows操作系统上的各种任务,包括自动化任务、网页开发、系统管理等。在这篇文章中,我们将探讨VBScript编程的优点和缺点。 优点: 1.易学易用:VBScript是一种易于学习和使用的编程语言。它的

    2024年02月04日
    浏览(38)
  • 网络基础知识&socket编程

    Linux 系统是依靠互联网平台迅速发展起来的,所以它具有强大的网络功能支持,也是Linux 系统的一大特点。互联网对人类社会产生了巨大影响,它几乎改变了人们生活的方方面面,可见互联网对人类社会的重要性! 本章我们便来学习一些网络基础知识,如果感兴趣的读者可以

    2024年02月10日
    浏览(43)
  • 一、网络编程之基础知识详解

    引言: 初学网络编程时会涉及到许多网络基础知识,这些知识点比较零碎,本文希望系统总结一次,以便在后续的学习和工作中能够快速查阅。 网络分层模型 OSI 七层模型 OSI 模型,也叫做七层模型, OSI 是 Open System Interconnection 的缩写,译为“开放式系统互联”。 OSI 模型是

    2024年02月09日
    浏览(57)
  • Linux网络编程 网络基础知识

    目录 1.网络的历史和协议的分成 2.网络互联促成了TCP/IP协议的产生 3.网络的体系结构 4.TCP/IP协议族体系 5.网络各层的协议解释 6.网络的封包和拆包 7.网络预备知识      Internet-\\\"冷战\\\"的产物 1957年十月和十一月,前苏联先后欧两颗”Spuinik”卫星上天 1958年美国总统艾森豪威尔向

    2024年02月10日
    浏览(44)
  • FPGA基础知识-编程语言接口

    目录 学习目标: 学习内容: 1.PLI的使用 2.PLI任务的连接和调用 3.内部数据的获取 4.PLI库子程序 学习时间: 学习产出: 解释在Verilog仿真中如何使用PLI子程序。 描述PLI的用途。 定义用户自定义系统任务和函数以及用户自定义C子程序。 理解用户自定义系统任务的连接和调用。

    2024年02月11日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包