多线程与高并发编程(六)【容器】

多线程容器

  • 一、容器补充知识(主要从多线程的使用角度上讲)
    • 1. 在物理结构上实际上容器只有两种:数组Array,链表Linked
    • 2. 在逻辑结构上的容器非常多。
    • 3. Collection一共三种List、Set、Queue(多线程最重要的)
    • 4. 容器发展过程
      •  1)从HashTable到CHM
      •  2)从Vector到Queue
      •  3)sync与cas
  • 二、多线程容器
    • 1. ConcurrentHashMap()
    • 2. ConcurrentSkipListMap()
    • 3. CopyOnWriteArrayList() 写时复制,写入东西时复制一份,源码中add写方法加了sync,get读时不用加锁
    • 4. BlockingQueue 阻塞队列
      • 1)ConcurrentLinkedQueue 非阻塞的
      • 2)LinkedBlockingQueue 阻塞的
    • 5. DelayQueue 时间的
    • 6. PriorityQueue 优先级的
    • 7. SynchronusQueue 阻塞的 容量为0 同步 线程池常用,用处最大
    • 8. TransferQueue 阻塞的
    • 面试题:

一、容器补充知识(主要从多线程的使用角度上讲)

多线程与高并发编程(六)【容器】_第1张图片

1. 在物理结构上实际上容器只有两种:数组Array,链表Linked

2. 在逻辑结构上的容器非常多。

3. Collection一共三种List、Set、Queue(多线程最重要的)

4. 容器发展过程

  在古老的JDK1.0中,容器有两个Vector、HashTable

 1)从HashTable到CHM

  ①Vector类似List,一个一个加;
   HashTable类似Map,一对一对加;
   不太合理处为方法内都加了sync,都完全加了锁;
   设计上也有缺陷,历史遗留物,基本不用了。
  ②后来加入了HashMap,完全不加锁,所以高并发下会有线程安全问题,与HashTable不同处也不只是锁。
  ③后来加入了Collections工具类,这个方法使得HashMap加了锁(锁的力度上比HashTable上小一点)。
在这里插入图片描述
  ④ConcurrentHashMap 简写CHM。在JUC包中
  ⑤高并发的情况下效率一定测试。
   下面的测试结果仅代表测试代码的环境,不代表这就是效率排名,因为sync不是一定比CAS的效率低的。
   测试高并发写效率(HashMap线程安全不能用):
   synchronizedHashMap > HashTable >> concurrentHashMap
   测试高并发读效率(日常中读的操作比较多,HashMap线程安全不能用):
   synchronizedHashMap < HashTable << concurrentHashMap

 2)从Vector到Queue

    ArrayList是没有锁的,所以是线程不安全的。
    多线程程序少考虑List。
  ①ConcurrentLinkedQueue() 钦定多线程(原子性操作)
    .add() 加入
    .poll() 取值
在这里插入图片描述
    使用CAS实现的原子性,并不是利用sync。

 3)sync与cas

    在特定环境下效率是不一定谁高。
    单线程用hashMap,LinkedList,ArrayList
    高并发下执行时间短用ConcurrentHashMap,ConcurrentQueue
    并发少下执行时间长用过sync
    但不是绝对的,任何时候都应该以实际压测为主。

二、多线程容器

  复习知识:
    Map总的包括两个HashMap、TreeMap
    HashMap是没有排序的,TreeMap内部用的是红黑树【是具有排序的,读效率高】

1. ConcurrentHashMap()

2. ConcurrentSkipListMap()

  [ConcurrentHashMap()是CAS操作的,但是为什么没有ConcurrentTreeMap,因为在树上CAS实现非常麻烦,所以有了这个跳表
  排序的。
  跳表插入、读取原理(之前写的《Redis入门基础》最后也有讲):
多线程与高并发编程(六)【容器】_第2张图片
  通过重点元素来缩小范围。

3. CopyOnWriteArrayList() 写时复制,写入东西时复制一份,源码中add写方法加了sync,get读时不用加锁

  原理:写的时候原元素t1复制一份t2,然后再在t2的末尾开辟一个新空间放入要插入的元素,然后写的指向和读的指向改为指向t2,因为Array长度是不变的所以需要复制,因为新老部分除了新增的完全一样所以读不用加锁。
  使用环境:读特别多,写少的环境。读的时候共享锁,写的时候排它锁。
  从此往下为Queue队列:
   Queue常用接口:
多线程与高并发编程(六)【容器】_第3张图片
   .offer()[@return boolean] 类似add(),但是list.add()取不出来会抛异常【offer还可以添加一些参数来在规定时间之后尝试加入】
   .size() 类似size()
   .poll() 拿出来数据,这个数据在队列中删除
   .peek() 拿出来数据,队列中保留

4. BlockingQueue 阻塞队列

1)ConcurrentLinkedQueue 非阻塞的

在这里插入图片描述

2)LinkedBlockingQueue 阻塞的

在这里插入图片描述
  链表实现的,没有长度限制,可以一直加到内存满了(或者integer的MAX)。
  在通用的接口上又加了两个方法:
  .put() 往里装,如果满了线程会阻塞住
  .take() 往外拿,如果空了线程会阻塞住
  所以天生就实现了生产者消费者模型。

  面试题:Queue和List区别到底在哪
  答:Queue添加了对线程友好的API,offer peek poll,在BlockingQueue进一步新增了put、take方法,可以阻塞,天生实现了生产者消费者模型。

5. DelayQueue 时间的

  按照等待时间来排序的。
在这里插入图片描述
  需要实现Delayed
多线程与高并发编程(六)【容器】_第4张图片
  重写compareTo(Delayed),确定如何根据时间比较的
  重写getDelay(TimeUnit) 拿到delay时间
  等待时间越短的越先执行(按急迫程度执行)
  .take() 从队列中拿出来一个
多线程与高并发编程(六)【容器】_第5张图片
  上图执行顺序t5 t1 t3 t2 t4
  用途:按时间进行任务调度

6. PriorityQueue 优先级的

  插入的元素会自动排序,从小到大,从a-z,内部实现是一个二叉树,堆排序中的最小堆或者说是小顶堆、小根堆
多线程与高并发编程(六)【容器】_第6张图片

7. SynchronusQueue 阻塞的 容量为0 同步 线程池常用,用处最大

  容量为0,装不了东西,不是为了装东西的,是为了给另一个线程下达任务的。
在这里插入图片描述
  用途:两个线程之间互相交换数据,很类似前文说到的Exchanger,不过exchanger需要两个线程都阻塞等待完成交换,而synchronusQueue则不需要,线程池中线程之间的联系都用他。表面看没用。

8. TransferQueue 阻塞的

在这里插入图片描述
  新增了.transfer(object) 放入队列
  与put不同为put是有一个线程装完数据就走了,而transfer则是装完阻塞等有人取走才离开干自己的事(可以比喻为装货之后等着拿钱才走)
  用途:提交一个订单进来,然后等着有人处理这个订单,才离开返回给客户一个反馈。(当然这过于底层了,一般都用MQ,如果真是自己写的java底层就可以用这个transferQueue)

面试题:

  前几篇中的一道面试题:
  问:两个线程,一个输出1-26,一个输出A-Z,要求交替输出,结果如1A2B3C…
  答(思路,有很多种,这里提出比较重要的几点):
    第一种用LockSupport,交替锁定解锁
    第二种用sync、wait、notify,来控制(原题实际上想考的点是这个,重点),思路也是一样的,sync锁定各个线程,一个先wait,然后另一个输出后notify那个等待的,下图截取一部分。
多线程与高并发编程(六)【容器】_第7张图片
多线程与高并发编程(六)【容器】_第8张图片

你可能感兴趣的:(多线程与高并发)