写点什么

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

作者:冰河
  • 2022 年 1 月 14 日
  • 本文字数:2529 字

    阅读完需:约 8 分钟

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

大家好,我冰河~~


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


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

分工

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


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

总结

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


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



发布于: 刚刚阅读数: 2
用户头像

冰河

关注

公众号:冰河技术 2020.05.29 加入

互联网高级技术专家,《深入理解分布式事务:原理与实战》,《海量数据处理与大数据技术实战》和《MySQL技术大全:开发、优化与运维实战》作者,mykit-data与mykit-transaction-message框架作者。【冰河技术】作者。

评论

发布
暂无评论
【高并发】要想学好并发编程,关键是要理解这三个核心问题