【高并发】学好并发编程,关键是要理解这三个核心问题

写在前面

写【高并发专题】有一段时间了,一些读者朋友留言说,并发编程很难,学习了很多的知识,但是在实际工作中却无从下手。对于一个线上产生的并发问题,又不知产生这个问题的原因究竟是什么。对于并发编程,感觉上似乎是掌握了,但是真正用起来却不是那么回事!

其实,造成这种现象的本质原因就是没有透彻的理解并发编程的精髓,而学好并发编程的关键是需要弄懂三个核心问题:分工、同步和互斥

分工

比较官方的解释为:分工就是将一个比较大的任务,拆分成多个大小合适的任务,交给合适的线程去完成,强调的是性能。

如果你还不能够理解什么是分工,这里,我们可以做一个假设。假设你是一个XXX上市公司的CEO,你的工作是如何管理好你的公司。但是,就如何管理好公司而言,涉及到的任务就比较多了,我们可以将其看做一个很大的任务,这个很大的任务,细看的话可以包括:人员招聘和管理、产品设计和开发、运营和推广、公司税务等等。那细化后这么多的任务交给你一个人去做,想必你一定是崩溃的。即使你能够挺住,估计你一个人把这所有的任务完成,那黄花菜也就凉了!到时,估计你就会偷偷的躲在角落里唱“凉凉”了。。。

所以,如果你真的想管理好你的公司,你就需要将这些任务分解,分工细化,将人员招聘和管理的任务交给人力资源部门去完成,将产品的设计交给设计部门去完成,将产品的开发交给开发部门去完成,将运营和推广交给运营和市场部门去完成,将公司税务交给财务部门去完成。此时,你的任务就是及时了解各个部门的工作情况,统筹并协调各部门的工作,并思考如何规划公司的未来。

其实,这里你将管理公司的任务拆解、细化分工之后,你会发现,其实各部门之间的工作是并行执行的。比如:人力资源部门在管理员工的绩效考核时,同时产品设计和开发部门正在设计和开发公司的产品,与此同时,公司的运营正在和设计与开发沟通如何更好的完善公司的产品,而推广部门正在加大力度宣传和推广公司的产品。而财务部门正在统计和计算公司的各种财务报表等。一切都是那么的有条不紊!

所以,安排合适的人去做合适的事情,在实际工作中是非常重要的。映射到并发编程领域也是同样的道理。如果将所有的任务交给一个线程执行,就好比将公司的所有事情交给你一个人去做一样。等到把事情做完了,黄花菜也凉了。所以,在并发编程中,我们同样需要将任务进行拆解,分工给合适的线程去完成。

在并发编程领域,还需要注意一个问题就是:分工给合适的线程去做。也就是说,应该主线程执行的任务不要交给子线程去做,否则,是解决不了问题的。这就好比一家公司的CEO将如何规划公司的未来交给一个产品开发人员去做一样,这不仅不能规划好公司的未来,甚至会与公司的价值观背道而驰。

在JavaSDK中的:Executor、Fork/Join和Future都是实现分工的一种方式。

同步

在并发编程中的同步,主要指的是一个线程执行完任务后,如何通知其他的线程继续执行,强调的是性能。

将任务拆分,并且合理的分工给了每个人,接下来就是如何同步每个人的任务了。

假设小明是一名前端开发人员,他渲染页面的数据需要等待小刚的接口完成,而小刚写接口又需要等待小李的服务开发完成。也就是说,任务之间是存在依赖关系的,前面的任务完成后,才能进行后面的任务。

对于实际工作中,这种任务的同步,大多数靠的是人与人之间的沟通,小李的服务写完了,告诉小刚,小刚则马上进行接口开发,等小刚的接口开发完成后,又告诉了小明,小明马上调用接口将返回的数据渲染在页面上。

这种同步机制映射到并发编程领域,就是一个线程的任务执行完毕之后,通知其他的后续线程执行任务。

对于这种线程之间的同步,我们可以使用下面的 if 伪代码来表示。

if(前面的任务完成){
    执行当前任务
}else{
    继续等待前面任务的执行
}

如果为了更能够及时的判断出前面的任务是否已经完成,我们也可以使用 while 伪代码来表示。

while(前面的任务未完成){
    继续等待前面任务的执行
}
执行当前任务

上述伪代码表示的意义是相同的:当线程执行的条件不满足时,线程需要继续等待,一旦条件满足,就需要唤醒等待的线程继续执行。

在并发编程领域,一个典型的场景就是生产者-消费者模型。当队列满时,生产者线程需要等待,队列不满时,需要唤醒生产者线程;当队列为空时,消费者线程需要等待,队列不空时,需要唤醒消费者。我们可以使用下面的伪代码来表示生产者-消费者模型。

  • 生产者
while(队列已满){
    生产者线程等待
}
唤醒生产者
  • 消费者
while(队列为空){
    消费者等待
}
唤醒消费者

在Java的SDK中,提供了一些实现线程之间同步的工具类,比如说:CountDownLatch、 CyclicBarrier 等。

互斥

同一时刻,只允许一个线程访问共享变量,强调的是线程执行任务的正确性。

在并发编程领域,分工和同步强调的是执行任务的性能,而线程之间的互斥则强调的是线程执行任务的正确性,也就是线程的安全问题。如果多个线程同时访问同一个共享变量,则可能会发生意想不到的后果,而这种意想不到的后果主要是由线程的可见性、原子性和有序性问题产生的。而解决可见性、原子性和有序性问题的核心,就是互斥。

关于互斥,我们可以用现实中的一个场景来描述:多个岔路口的车辆需要汇入一条道路中,而这条道路一次只能允许通过一辆车,此时,车辆就需要排队依次进入路口。

Java中提供的synchronized、Lock、ThreadLocal、final关键字等都可以解决互斥的问题。

例如,我们以synchronized为例来说明如何进行线程间的互斥,伪代码如下所示。

//修饰方法
public synchronized void xxx(){

}
//修饰代码块
public void xxx(){
    synchronized(obj){

    }
}
//修饰代码块
public void xxx(){
    synchronized(XXX.class){

    }
}
//修饰静态方法
public synchronized static void xxx(){

}

总结

并发编程旨在最大限度的利用计算机的资源,提高程序执行的性能,这需要线程之间的分工和同步来实现,在保证性能的同时,又需要保证线程的安全,这就又需要保证线程之间的互斥性。而并发编程的难点问题,往往又是由可见性、原子性和有序性问题导致的。所以,我们在学习并发编程时,一定要先弄懂线程之间的分工、同步和互斥。

写在最后

如果觉得文章对你有点帮助,请微信搜索并关注「 冰河技术 」微信公众号,跟冰河学习高并发编程技术。

最后,附上并发编程需要掌握的核心技能知识图,祝大家在学习并发编程时,少走弯路。

原文地址:https://www.cnblogs.com/binghe001/p/12676927.html

时间: 2024-08-30 07:56:04

【高并发】学好并发编程,关键是要理解这三个核心问题的相关文章

学并发编程,透彻理解这三个核心是关键

写在前面 上一篇文章这次走进并发的世界,请不要错过 给大家带了并发编程的开胃菜,接下来我们逐步上正餐,在吃正餐之前,我还要引用那首诗词: 「横看成岭侧成峰,远近高低各不同」,远看看轮廓,近看看细节,不断切换思维或视角来学习 远看并发,并发编程可以抽象成三个核心问题: 分工.同步/协作.互斥 如果你已经工作了,那么你一定听说过或者正在应用敏捷开发模式来交付日常的工作任务,我们就用你熟悉的流程来解释这三个核心问题 分工 将当前 Sprint 的 Story 拆分成「合适」大小的 Task,并且安排给

基于UDP协议的socket套接字编程 基于socketserver实现并发的socket编程

基于UDP协议 的socket套接字编程 1.UDP套接字简单示例 1.1服务端 import socket server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 建立一个UDP协议的服务器 server.bind(("127.0.0.1",8080)) while True: data,addr = server.recvfrom(1024) server.sendto(data.upper(),addr) server

并发编程解决的三个核心问题

并发编程解决的三个核心问题 1)分工 分工指的是如何高效地拆解任务并分配给线程 2)同步 同步指的是线程之间如何协作 3)互斥 互斥是保证同一时刻只允许一个线程访问共享资源 原文地址:https://www.cnblogs.com/756623607-zhang/p/10480820.html

Linux-Shell-使用mkfifo实现多任务并发及并发数控制

默认的情况下,Shell脚本中的命令是串行执行的,必须等到前一条命令执行完后才执行接下来的命令,但是如果我有一大批的的命令需要执行,而且互相又没有影响的情况下(有影响的话就比较复杂了),那么就要使用命令的并发执行了. 如下: #!/bin/bash IPLIST=/home/meta/ipinfo/iplist for i in $(cat ${IPLIST} |grep -viE "^#|备机|ts"|awk '{print $1}') do ssh $i "cd ~/up

高质量C++/C编程指南_读书笔记1

----------------------------------------------------------------------------- 引用和指针 ----------------------------------------------------------------------------- 以下程序中,n是m的一个引用(reference),m是被引用物(referent). int m; int &n = m; n相当于m的别名(绰号),对n的任何操作就是对m的

高质量c c++编程

第1章 文件结构 每一个C++/C程序通常分为两个文件.一个文件用于保存程序的声明(declaration),称为头文件.还有一个文件用于保存程序的实现(implementation),称为定义(definition)文件. C++/C程序的头文件以“.h”为后缀,C程序的定义文件以“.c”为后缀,C++程序的定义文件通常以“.cpp”为后缀(也有一些系统以“.cc”或“.cxx”为后缀). 1.1版权和版本号的声明 版权和版本号的声明位于头文件和定义文件的开头(參见演示样例1-1),主要内容有

[编写高质量iOS代码的52个有效方法](三)消息和运行期

[编写高质量iOS代码的52个有效方法](三)消息和运行期 参考书籍:<Effective Objective-C 2.0> [英] Matt Galloway 先睹为快 11.理解objc_msgSend的作用 12.理解消息转发机制 13.用"方法调配技术"调试"黑盒方法" 14.理解"类对象"的用意 目录 编写高质量iOS代码的52个有效方法三消息和运行期 先睹为快 目录 第11条理解objc_msgSend的作用 第12条理解

编程开发进阶更重要的是掌握的核心设计思维[图]

编程开发进阶更重要的是掌握的核心设计思维[图]:"单独写一个琐碎的代码块就等同于弹奏音阶一样,不幸的是,弹奏音阶并不能教会你任何关于音乐的东西,并且非常枯燥"这是 Eric S. Raymond 在他的文章<How to learn Hacking>中所描述的一段话.作为一个已经从入门走到进阶的编程者,我非常赞同他所说的这句话.然而,大部分老师,课本和大学课程都会通过琐碎的代码块练习来教授编程知识.即使这样的编程练习可以让你明白条件语句和循环的工作机制以及如何编写一个基本的

centos shell编程5LANMP一键安装脚本 第三十九节课

centos shell编程5LANMP一键安装脚本  第三十九节课 上半节课 下半节课 f