写点什么

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

用户头像
冰河
关注
发布于: 2020 年 10 月 21 日
【高并发】学好并发编程,关键是要理解这三个核心问题

写在前面


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


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


分工


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


如果你还不能够理解什么是分工,这里,我们可以做一个假设。假设你是一个 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(){    }
复制代码


总结


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


重磅福利


微信搜一搜【冰河技术】微信公众号,关注这个有深度的程序员,每天阅读超硬核技术干货,公众号内回复【PDF】有我准备的一线大厂面试资料和我原创的超硬核 PDF 技术文档,以及我为大家精心准备的多套简历模板(不断更新中),希望大家都能找到心仪的工作,学习是一条时而郁郁寡欢,时而开怀大笑的路,加油。如果你通过努力成功进入到了心仪的公司,一定不要懈怠放松,职场成长和新技术学习一样,不进则退。如果有幸我们江湖再见!


另外,我开源的各个 PDF,后续我都会持续更新和维护,感谢大家长期以来对冰河的支持!!


写在最后


如果你觉得冰河写的还不错,请微信搜索并关注「 冰河技术 」微信公众号,跟冰河学习高并发、分布式、微服务、大数据、互联网和云原生技术,「 冰河技术 」微信公众号更新了大量技术专题,每一篇技术文章干货满满!不少读者已经通过阅读「 冰河技术 」微信公众号文章,吊打面试官,成功跳槽到大厂;也有不少读者实现了技术上的飞跃,成为公司的技术骨干!如果你也想像他们一样提升自己的能力,实现技术能力的飞跃,进大厂,升职加薪,那就关注「 冰河技术 」微信公众号吧,每天更新超硬核技术干货,让你对如何提升技术能力不再迷茫!



发布于: 2020 年 10 月 21 日阅读数: 1169
用户头像

冰河

关注

公众号:冰河技术 2020.05.29 加入

Mykit系列开源框架发起者、核心架构师和开发者,《海量数据处理与大数据技术实战》与《MySQL开发、优化与运维实战》作者。【冰河技术】微信公众号作者。

评论 (5 条评论)

发布
用户头像
并发之所以有难度,是因为搞定调度问题是个难点,让多个子任务按照你的意愿工作是主要任务
2020 年 10 月 22 日 14:39
回复
是的,涉及到多线程,调度问题就会变得复杂
2020 年 10 月 22 日 20:01
回复
用户头像
同步为什么强调的是性能,不管是百度百科上的定义,还是你的定义, 看起来都是为了正确性,能帮忙解答一下么?
2020 年 10 月 22 日 09:31
回复
可以加我微信 sun_shine_lyz 讨论
2020 年 10 月 22 日 20:00
回复
同步不是为了什么性能,而是同步是符合程序员的思维,为了可读性, 但会带来性能问题
2020 年 10 月 29 日 10:10
回复
没有更多了
【高并发】学好并发编程,关键是要理解这三个核心问题