前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【高并发】学好并发编程,关键是要理解这三个核心问题!!

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

作者头像
冰河
发布2020-10-29 11:41:21
2100
发布2020-10-29 11:41:21
举报
文章被收录于专栏:冰河技术

作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。自开源半年多以来,已成功为十几家中小型企业提供了精准定时调度方案,经受住了生产环境的考验。为使更多童鞋受益,现给出开源框架地址:

https://github.com/sunshinelyz/mykit-delay

PS: 欢迎各位Star源码,也可以pr你牛逼哄哄的代码。

写在前面

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

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

分工

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

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

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

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

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

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

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

同步

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

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

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

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

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

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

代码语言:javascript
复制
if(前面的任务完成){
    执行当前任务
}else{
    继续等待前面任务的执行
}

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

代码语言:javascript
复制
while(前面的任务未完成){
    继续等待前面任务的执行
}
执行当前任务

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

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

  • 生产者
代码语言:javascript
复制
while(队列已满){
    生产者线程等待
}
唤醒生产者
  • 消费者
代码语言:javascript
复制
while(队列为空){
    消费者等待
}
唤醒消费者

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

互斥

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

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

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

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

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

代码语言:javascript
复制
//修饰方法
public synchronized void xxx(){
    
}
//修饰代码块
public void xxx(){
    synchronized(obj){
        
    }
}
//修饰代码块
public void xxx(){
    synchronized(XXX.class){
        
    }
}
//修饰静态方法
public synchronized static void xxx(){
    
}

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-10-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 冰河技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 写在前面
  • 分工
  • 同步
  • 互斥
相关产品与服务
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档