Java并发读书笔记(五)

第六章 Java并发容器和框架

一、ConcurrentHashMap

1.

为什么使用:HashMap不安全,HashTable效率低

这边得上网查

HashMap:并发执行put操作时会引起死循环,因为多线程会导致HashMap的Entry链表形成环形数据结构,Entry
的next节点永远不为空,就会产生死循环获取Entry

HashTable:HashTable使用synchronized来保证线程安全,在线程竞争激烈的情况下其效率十分低下

ConcurrentHashMap:使用锁分段技术:首先将数据分成一段一段地存储,然后给每一段数据配一把锁,
当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

2.ConcurrentHashMap的结构

Segment数组+HashEntry数组

segment是一种可重入锁,HashEntry用于存储键值对数据

一个segment里包含一个HashEntry数组,每个segment守护着一个HashEntry数组里的元素,
当对HashEntry数组的数据进行修改时,必须首先获得与它对应的segment锁

3、4初始化和定位segment

这里主要讲了hash的散列算法之类的,目前不用去研究

5.ConCurrentHashMap的操作

(1)get

整个get过程不需要加锁,除非读到的值是空的才会加锁重读。

做法:get方法里将要使用的共享变量都定义成volatile类型。定义成volatile的变量,能够在线程之间保持可见性,
能够被多线程同时读,并且保证不会读到过期的值,但是只能被单线程写。

之所以不会读到过期的值,是因为根据java内存模型的happens before原则,
对volatile变量的写入操作先于读操作。

上面这是用volatile替换锁的经典应用场景。

(2)put

加锁、扩容、定位添加

(3)size

先尝试两次不加锁的统计(直接累加各segment的count),比对modcount变量看是不是发生变化,如果发生变化再通过加锁的方式统计。

二、ConcurrentLinkedQueue

实现线程安全的队列两种方式:

1.阻塞方式:使用一个锁(入队出队同一个)或两个锁(入队出队不同锁)

2.非阻塞方式:使用循环CAS

1.入队

入队过程:

(1)将入队节点设置成当前队列尾节点的下一个节点

(2)更新tail节点,如果tail节点的next节点不为空,则将入队节点设置成tail节点,
如果tail节点的next节点为空,则将入队节点设置成tail的next节点,所以tail节点并不总是尾节点

为什么不设计成tail永远是尾节点?

这样做每次都需要使用循环cas更新tail节点,而大师的做法可以明显减少循环cas的次数,虽然会
增加定位真实尾节点的时间,但是实际开销还是减少了

2.出队

并不是每次出队时都更新head节点,当head节点里有元素时,直接弹出head里的元素,而不会更新head节点。
只有head节点里没有元素时,出队操作才会更新head节点。这种做法也是通过hops变量来减少使用CAS更新head节点的
消耗,从而提高出队效率。

三、Java中的阻塞队列

阻塞队列:支持阻塞的插入和移除方法

(1)支持阻塞的插入方法:当队列满时,队列会阻塞插入元素的线程,直到队列不满

(2)支持阻塞的移除方法:队列为空时,获取元素的线程会等待队列变为非空

抛出异常:add、remove
返回特殊值:offer、poll
一直阻塞:put、take

java提供了7种阻塞队列:

  1. ArrayBlockingQueue

数组实现的有界队列,FIFO,默认情况下不保证线程公平的访问队列

  1. LinkedBlockingQueue

用链表实现的有界阻塞队列,FIFO

  1. PriorityBlockingQueue

支持优先级的无界阻塞队列,默认情况下元素采取自然顺序升序排列,也可以自定义比较方法来指定元素排列规则

  1. DelayQueue

支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现Delay接口,在创建
元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。

DelayQueue的应用场景:

缓存系统的设计:用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelqyQueue,一旦能从DelayQueue
中获取元素,表示缓存有效期到了。
定时任务调度:使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,
比如TimerQueue就是使用DelayQueue实现的。
  1. SynchronousQueue、

不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。

SynchronousQueue可以看做一个传球手,负责把生产者线程处理的数据直接传递给消费者线程,队列本身并不
存储任何元素,非常适合传递性场景

  1. LinkedTransferQueue

由链表结构组成的无界阻塞TransferQueue队列,注意其transfer方法

  1. LinkedBlockingDeque

链表结构组成的双向阻塞队列

阻塞队列的实现

使用通知模式实现(condition)

四、Fork/Join框架

一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。

工作窃取算法

第七章 Java中的13个原子操作类

包含4种类型的原子更新方式:原子更新基本类型、原子更新数组、原子更新引用、原子更新属性(字段)

一、原子更新基本类型

AtomicBoolean,AtomicInteger,AtomicLong

其实就是用CAS实现,也可以采取源码中的思路自己写char、float、double的原子类

二、原子更新数组

AtomicLongArray,AtomicReferenceArray,AtomicIntegerArray

注意:上述三种会将传入的数组复制一份,当其对内部的数组进行修改时,不会影响到传入的数组

三、原子更新引用类型

AtomicReference,AtomicReferenceFieldUpdater,AtomicMarkableReference

四、原子更新字段类

创建更新器、

更新类的字段必须使用volatile

AtomicIntegerFieldUpdater,AtomicLongfieldUpdater,AtomicStampedReference

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
嗨!很高兴回答你关于Java并发编程的问题。请问你想知道什么方面的内容呢?我可以分享一些学习笔记和建议给你。 1. 并发编程基础:了解并发编程的基本概念,如线程、进程、锁、同步等。学习Java中的并发编程模型以及相关的API,如Thread、Runnable、Lock、Condition等。 2. 线程安全性:学习如何保证多线程环境下的数据安全性,了解共享资源的问题以及如何使用同步机制来防止数据竞争和并发问题。 3. 线程间的通信:掌握线程间的通信方式,如使用wait/notify机制、Lock/Condition等来实现线程的协调与通信。 4. 并发容器:学习并发容器的使用,如ConcurrentHashMap、ConcurrentLinkedQueue等。了解它们的实现原理以及在多线程环境下的性能特点。 5. 并发工具类:熟悉Java提供的并发工具类,如CountDownLatch、CyclicBarrier、Semaphore等,它们可以帮助你更方便地实现线程间的协作。 6. 并发编程模式:学习一些常见的并发编程模式,如生产者-消费者模式、读者-写者模式、线程池模式等。了解这些模式的应用场景和实现方式。 7. 性能优化与调试:学习如何分析和调试多线程程序的性能问题,了解一些性能优化的技巧和工具,如使用线程池、减少锁竞争、避免死锁等。 这些只是一些基本的学习笔记和建议,Java并发编程是一个庞大而复杂的领域,需要不断的实践和深入学习才能掌握。希望对你有所帮助!如果你有更具体的问题,欢迎继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值