目前 Copy-on-Write 在 Java 并发编程领域知名度不是很高,很多人都在无意中把它忽视了,但其实 Copy-on-Write 才是最简单的并发解决方案。
它是如此简单,以至于 Java 中的基本数据类型 String、Integer、Long 等都是基于 Copy-on-Write 方案实现的。Copy-on-Write 是一项非常通用的技术方案,
在很多领域都有着广泛的应用。不过,它也有缺点的,那就是消耗内存,每次修改都需要复制一个新的对象出来,好在随着自动垃圾回收(GC)
算法的成熟以及硬件的发展,这种内存消耗已经渐渐可以接受了。所以在实际工作中,如果写操作非常少,那你就可以尝试用一下 Copy-on-Write,效果还是不错的。
思考题
为什么java中没有 CopyOnWriteLinkedList
知识点:链表是分散的存储空间,通过 指针串联起来的
知识点:数组是连续的存储空间
当需要复制数组,只需要复制数组所在的连续空间即可,是一个时间复杂度为O(1)的操作。
而链表是不连续的存储空间,所以复制链表意味着需要 按照链表的指针,遍历整个链表。这将是一个时间复杂度为O(n)的操作。
笔记
线程封闭
局部变量
ThreadLocal
ThreadLocal源码
切记 ThreadLocalMap是Thread持有的
ThreadLocal 的内存泄漏问题
一个误区、不是说ThreadLocal一定存在内存泄漏,在一般场景中,Thread持有ThreadLocalMap,ThreadLocalMap以弱引用的方式持有ThreadLocal,当线程Thread被回收,意味着ThreadLocal一定会被回收。
ThreadLocal的内存泄漏发生在配合线程池使用的场景中
在线程池中 线程存活时间很长,往往同应用程序是同生共死的,这就意味着 Thread 持有的 ThreadLocalMap 一直都不会被回收,再加上 ThreadLocalMap 的 Entry对 ThreadLocal的引用是弱引用(WeakReference)
所以 只要 ThreadLocal 结束了自己的生命周期 是可以被回收掉的。但是 Entry 中的value 却是被Entry强引用。所以即便 Value的生命周期结束了,value 也是无法被回收的(可达性分析算法),从而导致内存泄漏
解决方案-一般ThreadLocal配合线程池使用,需要使用try{}finally{}手动释放资源
ExecutorService es; ThreadLocal tl; es.execute(()->{ //ThreadLocal增加变量 tl.set(obj); try { // 省略业务逻辑代码 }finally { //手动清理ThreadLocal tl.remove(); } });总结
Spring的 线程池管理 采用的就是 ThreadLocal ,每个线程可以针对性的修改自己的线程池。通过这个,可以写一个切面来切换数据源。
线程本地存储模式本质上是一种避免共享的方案,由于没有共享,所以自然也就没有并发问题。
31 | Guarded Suspension模式:等待唤醒机制的规范实现笔记
等待唤醒机制
场景:
服务调用方(生产者) 将消息 发动到 MQ。
服务提供方(消费者) 将消息 消费。
现在需要消费者消费完消息后,通知 生产者,然后生产者再响应其客户端。
分析
生产者需要等待
消费者消费完需要通知生产者
方案
分析场景,其实就是一个异步转同步的过程
生产者等待 消费者通知 可以使用 管程实现
管程采用Lock + Condition 实现等待 和 通知的关键 需要找到一个将 生产者发送消息 和 消费者响应消息,将两个消息对应到一个Condition
方案就是将消息对应的Condition缓存起来 这样的话就可以采用 **Key(msg.id) --> value(Condition)
当 上下游 生产者和消费者都是集群化部署,如何解决?