针对脑图的话,网上也是有比较多的。我谷歌的前几页,都看了一遍,最终筛选出来一个最详细的放在这里。分享给大家。
这篇文章主要的目的是来帮助我们构建多线程知识脉络。因为多线程的知识点是非常多的,记忆起来比较难,平常开发会用到多线程,但是不可能全部都用到。这么都知识点,哪一点用到的时候都是重点,所以很难说只记重点。记忆方法比较重要,先记住一个框架,然后再去展开。算法里边叫做广度优先遍历。
脑图是一个不错的构建知识框架的方式。网上的脑图,基本上都是根据《java并发边程艺术》这本书整理出来的书籍。
这篇文章五月一号整理出来了一个框架,后续还会继续完善的。
目录
详细的脑图
脑图解读——知识点分类
内存模型JMM
并发编程基础
并发相关的容器或者集合
并发编程工具类
CyclicBarrier
CountDownLatch
Semaphore
Exchanger
线程池
详细的脑图点击图片可以放大看,不过推荐右击下载下来看,下载下来推荐用2345看图王看,可以自由放大和拖拽,贼爽。
下边我会写一点自己的理解。只有图没有字没有灵魂。
脑图解读——知识点分类
· 其实看起多线程的知识点很多,整理出来,从脑图上看也就这个几个。
内存模型JMM 、 并发编程基础、锁、原子类、并发容器或者叫做并发集合、并发工具类(主要用来做先成通信)、以及线程池。
接下来再逐一击破。
内存模型JMM
主要包括了线程通信机制、内存模型、以及两个关键字 synchronized 和 volatile,其中这两个是需要弄明白背后的底层原理地。
并发编程基础
两大核心理论支撑:CAS 以及 AQS。这两个原理堪称是左膀右臂。
AQS 队列同步器AbstractQueuedSynchronizer为什么AQS地位如此之高呢?同步器是实现锁(也可以是任意同步组件)的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。可以这样理解二者之间的关系:锁是面向使用者的,它定义了使用者与锁交互的接口(比如可以允许两个线程并行访问),隐藏了实现细节;同步器面向的是锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等底层操作。锁和同步器很好地隔离了使用者和实现者所需关注的领域。
在AQS中,使用代码来完成了上锁和释放锁。同步器中写了获取锁和释放锁。
那获取锁失败了呢,如何做?这肯定要进入等待队列呀。这在AQS中叫做
锁
锁是并发编程的一个核心,它是保证多个线程之间数据共享时线程安全的手段。同时,锁使用不当,是非常非常容易给系统引入bug得,例如带来死锁。
基于JVM实现的重量锁 synchronized这个是基于JVM来实现的,一种重量级锁。synchronized是一种隐式锁,也就是关于锁的释放或者上锁都是基于本身留给我们的操作空间并不多。
Lock 是一个接口上边提到了synchronized是隐式锁,而Lock是一种显示锁。上边说synchronized没有操作的空间,那一般我们想要操作什么呢?这个要弄明白:其实我们想要操作的无非就是通过编码的方式获取锁以及释放锁、可以用代码来中断锁、以及可以对等待获取锁的操作添加一个时间。
lock只是一个接口,但是它确定了锁要做哪些事,如下:除了获取锁,还有一个尝试获取锁的方法。
Lock 的重要实现类 ReentrantLock
并发相关的容器或者集合
主要是线程安全相关的容器,其中包括了concurrentHashMap、concurrentLinkedQueue、concurrentSkipListMap
以及阻塞队列相关的,如下图,思考一个问题,阻塞队列是线程安全的吗?答案在这里:https://blog.csdn.net/littlehaes/article/details/104588339
特么怎么这么多,这怎么记,不可能每个都在编程的时候使用。记了不用还是会忘。别着急,试着从数据结构的角度去记忆。
整体上来说主要区分的是队列是否有界(这关乎这系统安全,对于无界阻塞队列,如果使用不当,会引发OOM异常)。然后在从数据结构的角度出发,就很好记了,总结如下:
其中由数据实现的一个并且它是无界的;链表实现的是三个,分别是无解的和有界的,另外一个是特殊数据结构双向链表;一个是基于堆实现的阻塞队列(它有特殊含义,可以让队列排序);另外两个不能用数据结构来记忆,因为都具有特殊的应用场景,分别是可以延时获取队列中的元素(DelayQueue),和只能存放一个元素的队列(这个很有意思 SynchronousQueue
),但是它用来做线程之间的通信。
DelayQueue的特点就是插入Queue中的数据可以按照自定义的delay时间进行排序。只有delay时间小于0的元素才能够被取出。如果没看够想看更多关于DelayQueue:https://zhuanlan.zhihu.com/p/138368078
SynchronousQueue
是一个内部只能包含一个元素的队列。插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素。同样,如果线程尝试获取元素并且当前不存在任何元素,则该线程将被阻塞,直到线程将元素插入队列。如果没看够再来个详解:https://www.jianshu.com/p/d5e2e3513ba3
再来一个详解的阻塞队列的图:
并发编程工具类
这里所谓的并发编程工具类,实际上是用来做先成的消息传递得。这是线程通信的两个途径之一。
CyclicBarrier下边的已经讲的比较清除了,我再用白话文讲一下:这个就好比,你在北京,分别越了上海的,青岛的,海南的朋友一起吃饭,并一起商量一下创业的事。最后要做的事是一起吃饭,那么不管哪个朋友先到都不能开饭,一定得等最后一个朋友过来才能一起吃饭。
CountDownLatch
这个和上边提到的差不多,都可以用吃饭的例子来解释。不过不同的是,上边的是可以重置的,今天约了三个地方的朋友,明天还可以再约一次。但是CountDownLatch是不能被重置的,今天约完,明天不能约了。CountDownLatch是这样的一个执行逻辑,好比是有一个门儿,上边上了三把锁,门外的人想要进去就要拿到三把钥匙,分别把三把锁都打开才能进去,同时有三把锁都打开了,你人才能进去。
Semaphore
通常用于限制访问某个资源的线程的个数。这就好比停车场,一共一百个车位,那就只能有一百辆车进去停车,超过以后,只能决绝停车。等里边有车开出来,你才能进去。
Exchanger
通常用作两个线程之间的通信,进行消息的传递,A把自己的消息传给B,B再回一个消息给A。
线程池
应该是归纳成这么几点:一是线程池工作原理,这个过程中要了解线程池的几个参数。一个是自定义线程池,因为工具类Excutors给我们提供的线程池,使用不当是非常有可能出现问题的。一般情况都要根据业务来自定义线程池,这包括如何定义线程数。ThreadPoolExcutor来自定义线程池。我个人觉得开源的项目中,Elasticsearch的自定义线程池是值得我们学习的。还有就是Future,也是一个重点。
Fork Join 框架