Java并发编程 | 并发编程的核心和本质

1,897 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第15天,点击查看活动详情

本系列专栏 Java并发编程专栏 - 元浩875的专栏 - 掘金 (juejin.cn)

前言

首先什么是并发编程,类比于我们现实世界,假如一个任务特别复杂,我们需要多个人相互合作来完成。而到了编程世界,这时也有一个任务非常复杂,我们就可以使用多线程来完成,这就是并发编程。

而并发编程不是一门相对独立的学科,而是一门综合学科,里面涉及的知识点很多,比如你需要了解CPU的缓存、操作系统线程的知识等。虽然在不同的编程语言都有各自的并发编程方式,但是他们的核心和本质都是不变的,本篇文章就来先说一下这个核心和本质。

正文

不论学习什么知识点,最忌讳的就是"盲人摸象",这里有个非常好的学习方法叫做"跳出来,看全景"和"钻进去,看本质",并发编程虽然知识很多,但是都可以抽象为3个核心问题:分工、同步和互斥

分工

所谓分工,类似于现实中你是一个项目经理,你需要组织完成一个项目,需要把这些任务拆分,安排合适的人员去做;在并发编程中,员工就相当于是线程,你需要合理分配任务给到线程,因为这会直接决定了并发程序的性能。

Java中有很多分工的SDK,比如Executor、Fork/Join本质都是一个分工方法,还包括生产者-消费者设计模式,这些思想都是如何把一个任务给合理的分配出去

同步

分好工之后就是执行了,在项目执行中,任务之间是有依赖性的,比如需求评审完之后要让开发人员开始编码,那怎么通知这个后续任务去开始呢 在现实生活中就是靠沟通协作,这是一项非常重要的事。

在并发编程中,主要就是指的是线程之间的协作,和现实生活中没有区别,就是一个线程执行完了一个任务,如何通知后续任务的线程开工

协作一般和分工是相关的,比如Future可以发起一个异步调用,当主线程通过get()方法获取结果时,主线程就会等待,当异步执行结果返回时,get()方法就自动返回了,这个主线程和异步线程之间的协作,Future工具类帮我做好了。

在工作中线程协作问题,一般可以归纳为:当某个条件不满足时,线程需要等待,当某个条件满足时,线程需要被唤醒执行。比如在生产者-消费者模型中,当队列满时,生产者线程等待,当队列不满时,生产者线程需要被唤醒执行;当队列为空时,消费者线程等待,当队列不为空时,消费者线程需要被唤醒执行。

互斥

分工、同步主要是强调性能,但在并发程序中还有一部分是关于正确性的,专业术语叫做"线程安全"。

在并发程序中,当多个线程同时访问一个共享变量时,结果是不确定的,而导致不确定的主要源头是可见性问题、有序性问题和原子性问题,为了解决这3个问题,Java语言引入了内存模型,内存模型提供了一系列的规则,利用这些规则,可以避免可见性、有序性问题,但是还不足以解决线程安全问题,解决线程安全的核心方案还是互斥。

互斥就是在同一时刻,只允许一个线程访问共享变量

实现互斥的核心技术就是锁,Java中的synchronized、SDK中的各种Lock都可以解决互斥问题,但是同时也带来性能问题,这就要分情况、分场景进行优化,比如ReadWriteLock、StampedLock可以优化读多写少的场景,还可以使用原子类这种无锁的数据结构。

总结

本篇文章作为并发编程专栏的开篇,意义还是非常重要的,因为想讲好一个涉及知识点很多的知识是非常不容易的,而如果可以看其本质,后面的学习就是不断印证和修改这些理论,这种学习就会容易很多。

下面还是做个总结,所有语言的并发编程都可以总结为3个核心和本质:

  • 分工:把一个任务合理地分配给多个线程,而分工的好不好,直接会影响任务的执行效率。
  • 同步:即一个线程完成它的任务时,需要通知后续的线程开始后续的任务。
  • 互斥:要保证共享变量在同一时刻,只有一个线程访问,这样才能保证这个变量是线程安全的。(这里就涉及到了线程安全问题,下篇文章我们就来说明。)