• 并发容器(Map、List、Set)实战及其原理


    目录

    一. JUC包下的并发容器

    1.1 CopyOnWriteArrayList

            1.1.1 引入

            概念

            1.1.2 场景

            1.1.3 黑名单实战

    1.1.4 CopyOnWriteArrayList原理

            1.1.5 CopyOnWriteArrayList 的缺陷


    一. JUC包下的并发容器

            Java的集合容器框架中,主要有四大类别:List、Set、Queue、Map,大家熟知的这些集合类ArrayList、LinkedList、HashMap这些容器都是非线程安全的。 所以,Java先提供了同步容器供用户使用。 同步容器可以简单地理解为通过synchronized来实现同步的容器 ,比如Vector、Hashtable以及SynchronizedList等容器。这样做的代价是削弱了并发性,当多个线程共同竞争容器级的锁时,吞吐量就会降低。
            因此为了解决同步容器的性能问题,所以才有了并发容器。 java.util.concurrent 包中提供了多种并发类容器:
           

    1.1 CopyOnWriteArrayList

            1.1.1 引入

            当我们遍历数组时加入,这时候有另一个线程也在往里添加数据的话,会报错如图:

            所以当我们使用  CopyOnWriteArrayList 时就可以解决
    1. package com.laoyang.Thread.JUC包下的并发容器;
    2. import java.util.ArrayList;
    3. import java.util.concurrent.CopyOnWriteArrayList;
    4. /**
    5. * @author:Kevin
    6. * @create: 2023-10-19 16:47
    7. * @Description: 对应ArrayList
    8. */
    9. public class CopyOnWriteArrayListDemo {
    10. public static void main(String[] args) {
    11. CopyOnWriteArrayList arrayList = new CopyOnWriteArrayList();
    12. // ArrayList arrayList = new ArrayList();
    13. arrayList.add("小杨");
    14. arrayList.add("小凯");
    15. arrayList.add("小文");
    16. new Thread(() -> {
    17. arrayList.add("test");
    18. }).start();
    19. for (Object o : arrayList) {
    20. System.out.println(o.toString());
    21. }
    22. }
    23. }

            概念

            CopyOnWriteArrayList 是 Java 中的一种线程安全的 List,它是一个可变的数组,支持并发读和写。与普通的 ArrayList 不同,它的读取操作不需要加锁,因此具有很高的并发性能。

            1.1.2 场景

            CopyOnWriteArrayList 的应用场景主要有两个方面:

            1. 读多写少的场景
            由于 CopyOnWriteArrayList 的读操作不需要加锁,因此它非常 适合在读多写少的场景中使用 。例如,一个读取频率比写入频率高得多的缓存,使用 CopyOnWriteArrayList 可以提高读取性能,并减少锁竞争的开销。
            2. 不需要实时更新的数据
            由于 CopyOnWriteArrayList 读取的数据可能不是最新的,因此它适合于不需要实时更新的数据。例如,在日志应用中,为了保证应用的性能,日志记录的操作可能被缓冲,并不是实时写入文件系统,而是在某个时刻批量写入。这种情况下,使用 CopyOnWriteArrayList 可以避免多个线程之间的竞争,提高应用的性能。

            1.1.3 黑名单实战

    1. package com.laoyang.Thread.JUC包下的并发容器;
    2. import java.util.Random;
    3. import java.util.concurrent.CopyOnWriteArrayList;
    4. /**
    5. * @author:Kevin
    6. * @create: 2023-10-19 19:35
    7. * @Description: 黑名单实战
    8. */
    9. public class IpDemo {
    10. private static final CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();
    11. //添加默认黑名单
    12. static {
    13. list.add("Ip0");
    14. list.add("Ip1");
    15. list.add("Ip2");
    16. }
    17. public static void main(String[] args) {
    18. Runnable runnable = new Runnable() {
    19. @Override
    20. public void run() {
    21. //模拟接入
    22. try {
    23. Thread.sleep(new Random().nextInt(5000));
    24. }catch (Exception e){}
    25. String currentIP = "Ip" + new Random().nextInt(3);
    26. if (list.contains(currentIP)){
    27. System.out.println(Thread.currentThread().getName() + " IP " +
    28. currentIP + "命中黑名单,拒绝接入处理");
    29. return;
    30. }
    31. System.out.println(Thread.currentThread().getName() + " IP " +
    32. currentIP + "接入处理...");
    33. }
    34. };
    35. new Thread(runnable, "thread1").start();
    36. new Thread(runnable, "thread2").start();
    37. new Thread(runnable, "thread3").start();
    38. new Thread(new Runnable() {
    39. @Override
    40. public void run() {
    41. try {
    42. Thread.sleep(new Random().nextInt(2000));
    43. }catch (Exception e){}
    44. String ip = "Ip3";
    45. list.add(ip);
    46. System.out.println(Thread.currentThread().getName() + " 添加了新的非法" +
    47. ip+ " + newBlackIP");
    48. }
    49. }).start();
    50. }
    51. }

            

    1.1.4 CopyOnWriteArrayList原理

            CopyOnWriteArrayList 内部使用了一种称为“写时复制”的机制。当需要进行写操作时,它会创建一个新的数组,并将原始数组的内容复制到新数组中,然后进行写操作。因此,读操作不会被写操作阻塞,读操作返回的结果可能不是最新的,但是对于许多应用场景来说,这是可以接受的。此外,由于读操作不需要加锁,因此它可以支持更高的并发度。

    1.1.5 CopyOnWriteArrayList 的缺陷

            由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致 young gc 或者 full gc
            不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个 set 操作后,读取到数据可能还是旧的,虽然 CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求;
            CopyOnWriteArrayList 合适读多写少的场景 ,不过这类慎用。因为谁也没法保证CopyOnWriteArrayList 到底要放置多少数据,万一数据稍微有点多,每次 add/set 都要重新复制数组,这个代价实在太高昂了。在高性能的互联网应用中,这种操作分分钟引起故障。

    2. ConcurrentHashMap 

            ConcurrentHashMap 是 Java 中线程安全的哈希表,它支持高并发并且能够同时进行读写操作。

            JDK1.8之前:  ConcurrentHashMap使用分段锁以在保证线程安全的同时获得更大的效率。

            JDK1.8之后: 开始舍弃了分段锁,使用自旋+CAS+synchronized关键字来实现同步。        

            原因:

                    一、 节省内存空间 

                    二、分段锁需要更多的内存空间,而大多数情况下,并发粒度达不到设置的粒度,竞争概率较小,反而导致更新的长时间等待(因为锁定一段后整个段就无法更新了)

                    三、提高GC效率

            

    2.1 应用场景

            ConcurrentHashMap 的应用场景包括但不限于以下几种:

            1. 共享数据的线程安全:在多线程编程中,如果需要进行共享数据的读写,可以使用 ConcurrentHashMap 保证线程安全。
            2. 缓存:ConcurrentHashMap 的高并发性能和线程安全能力,使其成为一种很好的缓存实现方案。在多线程环境下,使用 ConcurrentHashMap 作为缓存的数据结构,能够提高程序的并发性能,同时保证数据的一致性。
    (例如springbean容器里就是用来进行bean对象信息的缓存)

    2.2 基本用法(除了最基本的)

    V putIfAbsent(K key, V value)
    如果 key 对应的 value 不存在,则 put 进去,返回 null。否则不 put,返回已存在的 value。
    boolean remove(Object key, Object value)
    如果对应的key-value存在,就删除key-value,然后返回true,否则就不删除,返回false
    boolean replace(K key, V oldValue, V newValue)
    如果 key 对应的当前值是 oldValue,则替换为newValue,返回 true。否则不替换,返回 false。

    统计文件中英文字母出现的总次数

    1. package com.laoyang.Thread.JUC包下的并发容器;
    2. import java.util.Random;
    3. import java.util.concurrent.ConcurrentHashMap;
    4. import java.util.concurrent.CountDownLatch;
    5. import java.util.concurrent.atomic.AtomicLong;
    6. /**
    7. * @author:Kevin
    8. * @create: 2023-10-20 13:03
    9. * @Description: 统计文件中每个字母的个数
    10. */
    11. public class ConcurrentHashMapDemo {
    12. private static ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap<>();
    13. private static String[] words = {"yk", "gp", "xg"};
    14. //统计线程控制保持3个
    15. private static CountDownLatch countDownLatch = new CountDownLatch(3);
    16. public static void main(String[] args) {
    17. Runnable runnable = new Runnable() {
    18. @Override
    19. public void run() {
    20. for (int i = 0; i < 3; i++) {
    21. String word = words[new Random().nextInt(3)];
    22. AtomicLong wordnum = concurrentHashMap.get(word);
    23. //那就需要初始化放入
    24. if (wordnum == null) {
    25. AtomicLong newNum = new AtomicLong(0);
    26. wordnum = concurrentHashMap.putIfAbsent(word, newNum);
    27. if (wordnum == null) {
    28. wordnum = newNum;
    29. }
    30. }
    31. //在获取到的情况下直接+1
    32. wordnum.getAndIncrement();
    33. System.out.println(Thread.currentThread().getName() + ":" + word +
    34. " 出现" + wordnum + " 次");
    35. }
    36. countDownLatch.countDown();
    37. }
    38. };
    39. new Thread(runnable, "线程1").start();
    40. new Thread(runnable, "线程2").start();
    41. new Thread(runnable, "线程3").start();
    42. try {
    43. countDownLatch.await();
    44. System.out.println(concurrentHashMap.toString());
    45. } catch (Exception e) {
    46. }
    47. }
    48. }
  • 相关阅读:
    linux系统中通配符与常用转义字符
    【最小共因数函数】
    将JSON数据转换成JAVA的实体类
    [好题][曼哈顿距离][二分]Taxi 2022杭电多校第3场 1011
    【源码解读】VUE2.0和VUE3.0响应式区别?
    lodash笔记(集合篇)
    java-php-python+nodejs+vue生物遗传病的治疗和防范系统
    lintcode 581 · 最长重复子序列【中等 vip 动态规划 /递归】
    mmcls 多标签模型部署在torch serve
    C++ 求水仙花数
  • 原文地址:https://blog.csdn.net/qq_67801847/article/details/133930255