背景
在设计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框架)来简化并发编程。