并发编程的几个挑战

本文探讨并发编程中的挑战,包括上下文切换、死锁和资源限制问题。通过实例对比串行与并行执行速度,揭示并行不一定快的原因。并提供避免死锁的策略,以及面对资源限制时的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

并发编程的目的是为了让我们的程序变得更快,但是,并不是启动更多的线程就能让程序最大限度地并发执行。线程不在于多,在并发编程中,我们将面临如下挑战:上下文切换、死锁问题、受限于软硬件资源限制问题。

上下文切换

单核CPU是如果实现多线程的呢?

CPU通过给每个线程分配时间片来实现,时间片是分配给线程的时间。因为时间片非常短,所以CPU不停地切换线程执行,我们感觉上是同时执行的。(一般的,时间片只有十几毫秒 ms

当一个任务执行一个时间片之后,会切换成下一个任务执行,但是切换之前会保存这个任务的状态,以便下次回到这个任务的时候,可以下载上一次的状态,继续执行。这样,一个任务从保存到再加载的过程就是一次上下文切换。

上面给出了时间片上下文切换的解释。不只是计算机遵循这些原则。

拿现实生活中的例子,我们阅读英文书籍,遇到不会的单词,记住读到的位置,然后去查词典,查会了之后,我们回到上次卡住的位置继续阅读。想到回到原来的位置,大脑必须得记住在什么位置。这样周而复始,查单词和读书之间的切换是会影响读书效率的。这样也就不难理解,上下文切换对多线程的影响了。

多线程一定快吗?根据一个代码的实例,来探讨一下这个问题:

package com.cuteximi.concurrent;

/**
 * @program: Java-300
 * @description: 多线程一定快吗?
 * @author: TSL
 * @create: 2018-10-11 14:22
 **/
public class TestConcurrent {
    private static final long count = 10000;

    public static void main(String[] args) throws InterruptedException {
        concurrency();
        serial();
    }
    // 并行
    private static void concurrency() throws InterruptedException{
        long start = System.currentTimeMillis();
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                int a = 0;

                for (int i = 0;i< count;i++){
                    a+=5;
                }

            }
        });
        thread.start();
        int b = 0;
        for (long i = 0;i<count;i++){
            b--;
        }
        long time = System.currentTimeMillis() - start;

        thread.join();

        System.out.println("concurrency: "+time+" ms,b="+b);
    }
    // 串行
    private static void serial(){
        long start = System.currentTimeMillis();
        int a =0;
        for (long i = 0;i < count;i++){
            a+=5;
        }
        int b =0;
        for (long i = 0;i < count;i++){
            b--;
        }
        long time = System.currentTimeMillis() - start;

        System.out.println("Serial "+time+" ms,b="+b+",a="+a);
    }
}

经过不断的修改,上述代码的 count 的值。对比两种方式的执行速度。

count的值串行方法耗时(ms)并行方法耗时(ms)并行比串行快多少
1万01
10万34
100万66差不多
1000万1811
1亿16079快了一倍多

上表的数据是一个平均值,但是很容易看出多线程就不一定快!
那么一开始的时候为什么串行比并行还要快呢?是因为并行存在创建线程和上下文切换的花销。随着数量的扩大,多线程才显示出优势。

我们不得不重视,上下文切换的开销!

死锁

?是一个很有用的工具,使用锁的过程中最容易出现的就是死锁,一旦产生死锁就会造成系统不可用。看下面这段代码:

package com.cuteximi.concurrent;

/**
 * @program: Java-300
 * @description: 死锁的例子
 * @author: TSL
 * @create: 2018-10-11 15:14
 **/
public class DeadLockDemo {
    private static String A = "A";
    private static String B = "B";

    public static void main(String[] args) {
        deadLock();
    }
    private static void deadLock(){
        // 线程1
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (A){
                    try {
                        Thread.sleep(2000);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    synchronized (B){
                        System.out.println("111111");
                    }
                }
            }
        });

        // 线程2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (B){
                    synchronized (A){
                        System.out.println("222222222");
                    }
                }
            }
        });

        t1.start();
        t2.start();
    }
}

执行这段代码的时候,一直结束不了,因为线程1 和线程2在互相等待。

如何避免死锁呢?

  • 避免一个线程同时获得多把锁。
  • 避免一个线程在?内同时获得多个资源。尽量保证每把锁仅占用一个资源。
  • 尝试使用定时锁,使用lock.tryLock(timeout)来代替内部锁。
  • 对于数据库锁,加锁和解锁必须在一个数据库连接里面,否则会出现解锁失败的情况。

资源限制

程序执行速度受限于极端及硬件或软件资源,叫做资源限制。

  • 硬件限制:比如 带宽、硬盘读写速度、CPU处理速度等。
  • 软件限制:比如 数据库连接数,sock连接数等。

对于硬件的限制,可以考虑使用集群。比如使用 ODPS、Hadoop或者自己搭建集群。

对于软件资源的限制,可以考虑使用资源池将资源复用。

小结

本文主要说明了在并发编程中遇到几个的挑战,建议使用 JDK 并发包中的提供的并发容器和工具类来解决这一类问题。

资源下载链接为: https://pan.quark.cn/s/22ca96b7bd39 在当今的软件开发领域,自动化构建与发布是提升开发效率和项目质量的关键环节。Jenkins Pipeline作为一种强大的自动化工具,能够有效助力Java项目的快速构建、测试及部署。本文将详细介绍如何利用Jenkins Pipeline实现Java项目的自动化构建与发布。 Jenkins Pipeline简介 Jenkins Pipeline是运行在Jenkins上的一套工作流框架,它将原本分散在单个或多个节点上独立运行的任务串联起来,实现复杂流程的编排与可视化。它是Jenkins 2.X的核心特性之一,推动了Jenkins从持续集成(CI)向持续交付(CD)及DevOps的转变。 创建Pipeline项目 要使用Jenkins Pipeline自动化构建发布Java项目,首先需要创建Pipeline项目。具体步骤如下: 登录Jenkins,点击“新建项”,选择“Pipeline”。 输入项目名称和描述,点击“确定”。 在Pipeline脚本中定义项目字典、发版脚本和预发布脚本。 编写Pipeline脚本 Pipeline脚本是Jenkins Pipeline的核心,用于定义自动化构建和发布的流程。以下是一个简单的Pipeline脚本示例: 在上述脚本中,定义了四个阶段:Checkout、Build、Push package和Deploy/Rollback。每个阶段都可以根据实际需求进行配置和调整。 通过Jenkins Pipeline自动化构建发布Java项目,可以显著提升开发效率和项目质量。借助Pipeline,我们能够轻松实现自动化构建、测试和部署,从而提高项目的整体质量和可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值