并发和并行的区别

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 并发和并行的区别

背景

在设计Arpro第三版的时候马总提出了一个问题,我们认为人家表达是并发问题,但是由于并发边界不清晰,对这个问题没有详细的认知。

过程

例子:

并发的例子:

假设有一个多用户的聊天应用程序,多个用户可以同时发送消息并接收消息。当用户A发送一条消息时,用户B也可以同时发送另一条消息。这些消息的发送和接收是并发执行的,它们可以交替进行,但每个用户的操作都可能在任意时刻被暂停,切换到其他用户的操作,然后再恢复执行。这里的关键是多个用户的操作可以同时进行,但并不一定是同时完成。

并行的例子:

假设有一个图像处理应用程序,需要对一张大图进行处理,包括图像分割、滤镜处理和图像合并。如果使用并行处理,可以将图像分成多个块,然后分配给多个处理器核心同时进行处理。每个处理器核心独立处理一个图像块,最后将处理结果合并成最终的图像。在这个例子中,不同的图像块可以并行地进行处理,每个处理器核心负责一个块的处理,从而加速了整体的图像处理过程。

定义:

并发是指多个任务在同一时间段内执行,并且这些任务可以交替执行,每个任务都有可能在任意时刻暂停、切换到其他任务,然后再恢复执行。并行是指多个任务同时执行,每个任务都在不同的处理器核心或计算资源上独立运行,彼此之间相互独立,不会相互干扰。

并发和并行的区别在于任务的执行方式和资源的分配方式。并发注重任务的交替执行和资源的共享,通过合理的调度和同步机制来实现高效的任务处理;而并行注重任务的同时执行和资源的分配,通过利用多个处理器核心或计算资源来实现任务的加速。

并发解决办法(Redisson):

Redisson是一个Java库,提供了在分布式系统中处理并发访问的简单而高效的方法。它使用Redis作为后端存储,并提供了一系列分布式数据结构和同步机制,可以在分布式环境中处理并发访问。

通过Redisson,您可以使用分布式锁来协调对共享资源的访问,确保只有一个线程或进程可以同时访问该资源。这样可以避免多个线程或进程同时修改共享资源导致的数据一致性问题。

此外,Redisson还提供了分布式的原子操作、分布式集合和分布式消息传递等功能,可以帮助您在分布式系统中进行并发控制和协作。通过使用Redisson提供的这些功能,您可以有效地处理并发访问共享资源的问题。

解决并发步骤(Redisson):

<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.17.4</version>
        </dependency>
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
import java.util.ArrayList;
import java.util.Iterator;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.config.BaseConfig;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.redisson.config.SentinelServersConfig;
import org.redisson.config.SingleServerConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.util.StringUtils;
@Configuration
@ConditionalOnMissingBean({RedissonClient.class})
@Import({RedissonConfig.class})
public class RedissonClientConfig {
    private final Config config;
    RedissonClientConfig(Config config) {
        this.config = config;
    }
    @Bean
    public RedissonClient redissonClient() {
        return Redisson.create(this.config);
    }
    @ConditionalOnMissingBean({Config.class})
    @EnableConfigurationProperties({RedisProperties.class})
    static class RedissonConfig {
        @Autowired
        private RedisProperties redisProperties;
        RedissonConfig() {
        }
        @Bean
        public Config redissonConfig() {
            Config config = new Config();
            ArrayList clusterNodes;
            Iterator var5;
            String node;
            if (this.redisProperties.getSentinel() != null) {
                SentinelServersConfig sentinelServersConfig = config.useSentinelServers();
                RedisProperties.Sentinel sentinel = this.redisProperties.getSentinel();
                sentinelServersConfig.setMasterName(sentinel.getMaster());
                clusterNodes = new ArrayList();
                var5 = sentinel.getNodes().iterator();
                while(var5.hasNext()) {
                    node = (String)var5.next();
                    clusterNodes.add("redis://" + node);
                }
                sentinelServersConfig.addSentinelAddress((String[])clusterNodes.toArray(new String[clusterNodes.size()]));
                sentinelServersConfig.setDatabase(this.redisProperties.getDatabase());
                sentinelServersConfig.setPingConnectionInterval(60000);
                this.baseConfig(sentinelServersConfig, this.redisProperties);
            } else if (this.redisProperties.getCluster() != null) {
                ClusterServersConfig clusterServersConfig = config.useClusterServers();
                RedisProperties.Cluster cluster = this.redisProperties.getCluster();
                clusterNodes = new ArrayList();
                var5 = cluster.getNodes().iterator();
                while(var5.hasNext()) {
                    node = (String)var5.next();
                    clusterNodes.add("redis://" + node);
                }
                clusterServersConfig.addNodeAddress((String[])clusterNodes.toArray(new String[cluster.getNodes().size()]));
                clusterServersConfig.setFailedSlaveReconnectionInterval(cluster.getMaxRedirects());
                clusterServersConfig.setPingConnectionInterval(6000);
                this.baseConfig(clusterServersConfig, this.redisProperties);
            } else {
                SingleServerConfig singleServerConfig = config.useSingleServer();
                String schema = this.redisProperties.isSsl() ? "rediss://" : "redis://";
                singleServerConfig.setAddress(schema + this.redisProperties.getHost() + ":" + this.redisProperties.getPort());
                singleServerConfig.setDatabase(this.redisProperties.getDatabase());
                singleServerConfig.setPingConnectionInterval(60000);
                this.baseConfig(singleServerConfig, this.redisProperties);
            }
            config.setCodec(new JsonJacksonCodec());
            config.setLockWatchdogTimeout(30000L);
            return config;
        }
        private void baseConfig(BaseConfig config, RedisProperties properties) {
            if (!StringUtils.isEmpty(properties.getPassword())) {
                config.setPassword(properties.getPassword());
            }
            if (properties.getTimeout() != null) {
                config.setTimeout(Long.valueOf(properties.getTimeout().getSeconds() * 1000L).intValue());
            }
            if (!StringUtils.isEmpty(properties.getClientName())) {
                config.setClientName(properties.getClientName());
            }
        }
    }
}
@Autowired
    RedissonClient redisson;
    private static final String LOCK_KEY_PREFIX = "lock_startPush_";
    protected String getLockKey(String courseId) {
        return LOCK_KEY_PREFIX + courseId;
    }
 public void startFromFirst(@RequestBody Course course) {
        try {
            ContentGrain contentGrainFirst = contentGrainList.findFirstContentGrain(course);
            this.setContentGrain(contentGrainFirst);
            contentGrainFirst.setContentGrainController(this);
            // 获取锁 固定的key
            RLock redissonLock = redisson.getLock(getLockKey(String.valueOf(course.getId())));
            // 如果其他线程在使用锁,提示用户课程正在同步中!
            if (redissonLock.isLocked()) {
                log.error("课程在在被锁住");
            }
            try {
                redissonLock.lock();
//这一句是要锁住的语句
                contentGrainFirst.start();
            } catch (Exception e) {
                log.error("lock 失败", e);
            } finally {
                // 删除key
                redissonLock.unlock();
            }
        } catch (Exception e) {
            log.error("保存颗粒error", e);
        }
    }

总结

并发是指在一个时间段内同时处理多个任务的能力。它强调任务的独立性和相互之间的交替执行。在并发模型中,多个任务交替执行,并且它们可以通过切换上下文(Context Switching)来共享系统资源。这种切换可以是基于时间片轮转(Time-slicing)的或者是基于事件触发的。并发常用于提高系统的响应能力、资源利用率和用户体验。

并行是指同时执行多个任务的能力。它强调任务的同时性和真正的并行执行。在并行计算中,多个任务同时进行,每个任务可以独立地在不同的处理单元(如多核处理器、分布式系统等)上执行。并行通常用于加速任务的处理速度,提高计算能力和处理大规模数据。

简而言之,"并发"关注的是多个任务如何交替执行,而"并行"关注的是多个任务如何同时执行。并发主要强调任务的独立性和资源共享,而并行主要强调任务的同时性和加速处理。

需要注意的是,并发和并行并不是互斥的概念。在某些情况下,可以同时使用并发和并行来提高系统的效率和性能。例如,在一个多核处理器上,可以通过并发执行多个任务,同时利用并行处理来加速每个任务的处理过程。

解决并发问题的其他方式:

互斥锁(Mutex):使用互斥锁来确保共享资源在同一时间只能被一个线程访问。当一个线程获得了互斥锁,其他线程需要等待该线程释放锁才能继续执行。这种方式可以避免多个线程同时对共享资源进行写操作导致的数据不一致性。

信号量(Semaphore):使用信号量来控制对共享资源的访问。信号量可以用来表示资源的可用数量,当资源被占用时,其他线程可以等待信号量变为可用再继续执行。通过适当设置信号量的值,可以控制并发访问共享资源的数量。

读写锁(Read-Write Lock):读写锁是一种特殊的锁,允许多个线程同时读取共享资源,但只允许一个线程进行写操作。这样可以提高读取性能,同时确保写操作的独占性。

条件变量(Condition Variable):条件变量用于线程间的通信和同步。一个线程可以等待某个条件变量满足特定条件,而其他线程可以通过触发条件变量来通知等待的线程继续执行。

并发数据结构:使用特定的并发数据结构可以避免共享资源的竞争。例如,使用并发队列(Concurrent Queue)可以实现多个线程之间的安全消息传递。

任务调度和协作:使用合适的任务调度和协作机制,将任务划分为适当的子任务,并进行调度和协调,以提高并发执行的效率和性能。例如,使用线程池(Thread Pool)来管理和复用线程,或者使用并发框架(如Java中的Executor框架)来简化并发编程。


相关文章
|
12月前
|
Java
Java“NullPointerException”解决
Java中的“NullPointerException”是常见的运行时异常,发生在尝试使用null对象实例的方法或字段时。解决方法包括:1. 检查变量是否被正确初始化;2. 使用Optional类避免null值;3. 增加空指针检查逻辑。
1889 2
|
11月前
|
机器学习/深度学习 存储 数据采集
大数据性能优化
【10月更文挑战第24天】
583 3
|
分布式计算 大数据 数据处理
「大数据」Kappa架构
**Kappa架构**聚焦于流处理,用单一处理层应对实时和批量数据,消除Lambda架构的双重系统。通过数据重放保证一致性,简化开发与维护,降低成本,提升灵活性。然而,资源消耗大,复杂查询处理不易。关键技术包括Apache Flink、Spark Streaming、Kafka、DynamoDB等,适合需实时批量数据处理的场景。随着流处理技术进步,其优势日益凸显。
639 0
「大数据」Kappa架构
|
数据库 关系型数据库 MySQL
DTS库支持多种数据源类型
【6月更文挑战第2天】DTS库支持多种数据源类型
254 3
|
Android开发 Kotlin
Android面试题 之 Kotlin DataBinding 图片加载和绑定RecyclerView
本文介绍了如何在Android中使用DataBinding和BindingAdapter。示例展示了如何创建`MyBindingAdapter`,包含一个`setImage`方法来设置ImageView的图片。布局文件使用`&lt;data&gt;`标签定义变量,并通过`app:image`调用BindingAdapter。在Activity中设置变量值传递给Adapter处理。此外,还展示了如何在RecyclerView的Adapter中使用DataBinding,如`MyAdapter`,在子布局`item.xml`中绑定User对象到视图。关注公众号AntDream阅读更多内容。
224 1
|
算法 IDE 编译器
调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配
调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配
|
存储 机器学习/深度学习 数据采集
最新大厂数据湖面试题,知识点总结(一)
本文是一篇数据湖的面试题,同时也是数据湖知识点的讲解
650 0
|
前端开发 算法 测试技术
【软考学习5】流水线基本概念、周期执行时间、吞吐率、加速比和效率的计算
【软考学习5】流水线基本概念、周期执行时间、吞吐率、加速比和效率的计算
1368 0
|
存储 大数据 关系型数据库
开发大数据的正确姿势--交互式分析
在大数据技术领域里,用户通常希望获得高可靠、低延时的数据服务,来满足简单或者复杂的查询场景。本文为您深度揭秘交互式分析的核心技术以及应用场景,用交互式分析打开开发大数据的正确姿势!
3390 0
|
应用服务中间件 nginx 监控
如何检测 Web 服务请求丢失问题
导读 『StabilityGuide』是阿里多位阿里技术工程师共同发起的稳定性领域的知识库开源项目,涵盖性能压测、故障演练、JVM、应用容器、服务框架、流量调度、监控、诊断等多个技术领域,以更结构化的方式来打造稳定性领域的知识库,欢迎您的加入。
2816 95