美文网首页
十二、线程安全的集合

十二、线程安全的集合

作者: blank_white | 来源:发表于2020-07-19 20:58 被阅读0次

十二、线程安全的集合

除了使用锁来保护共享数据结构,也可以直接使用一些实现了线程安全的对象

常见的线程安全集合

        // 以下集合都可以被多线程安全访问

        // 无边界非阻塞队列
        ConcurrentLinkedDeque<String> concurrentLinkedDeque = new ConcurrentLinkedDeque<>();



        // 哈希映射
        ConcurrentHashMap<Integer,String> concurrentHashMap = new ConcurrentHashMap<>();

        // 16: 桶数
        // 0.75f: 负载因子
        // 16(第三个参数):并发写线程估计数,默认为 16 ,同一时间超过这个线程数,其他线程将被阻塞
        ConcurrentHashMap<Integer, String> concurrentHashMap2 = new ConcurrentHashMap<>(16,0.75f,16);

        // 有序集,(自动按大小排序,由小到大)
        ConcurrentSkipListSet<String> concurrentSkipListSet = new ConcurrentSkipListSet<>();
        // 有序映射 (键的大小顺序)
        ConcurrentSkipListMap<Integer,String> concurrentSkipListMap = new ConcurrentSkipListMap();

线程安全集合之 Set

       ConcurrentHashMap<Integer,Integer> concurrentHashMap=new ConcurrentHashMap<>();
        concurrentHashMap.put(3,3);
        concurrentHashMap.put(4,4);

        // ConcurrentHashMap.KeySetView<Object, Boolean> objects = concurrentHashMap.newKeySet();
        // 获得的Set 是 concurrentHashMap 的一个包装器,对 set 进行操作并不会影响 concurrentHashMap 的实际值
        Set<Integer> set = concurrentHashMap.newKeySet();
        set.add(1);
        set.remove(3);
        set.remove(4);
        System.out.println(set); // [1]
        System.out.println(concurrentHashMap); // {3=3, 4=4}

        // 获得的 Set 是 Map 的映射的键集,对 Set 的操作会影响到实际的 Map
        // 可以设置一个默认 value ,当添加的 set 不存在时,会为map设置此默认值为 value
        // 如果使用不设置此默认值的方法,则无法使用 add 方法
        Set<Integer> set2=concurrentHashMap.keySet(0);
        set2.add(2);
        set2.remove(3);
        set2.add(3);
        set2.remove(4);
        System.out.println(set2); // [2, 3]
        System.out.println(concurrentHashMap); // {2=0, 3=0}

map 的原子更新

        ConcurrentHashMap<Integer, String> concurrentHashMap = new ConcurrentHashMap<>();

        concurrentHashMap.put(1,"1 oldValue");
        String oldValue;
        String newValue;

        oldValue=concurrentHashMap.get(1);
        // put 方法虽然是线程安全的,但是在这个位置可能会被其他线程插入,concurrentHashMap.get(1) 被改变
        newValue=oldValue.replaceAll("old","new");
        concurrentHashMap.put(1,newValue);

        //////////// 线程安全的更新 方式 1
        newValue="1 newValue";
        do {
            oldValue=concurrentHashMap.get(1);
        }while (!concurrentHashMap.replace(1,oldValue,newValue));


        //concurrentHashMap.put(1,null);
        //////////// 线程安全的更新 方式 2
        // compute方法,为第二个参数传入 新值获得过程
        // ConcurrentHashMap 不允许 null 作为值,所以可以用 null 判断键对应值是否存在
        // 处理 键和值
        concurrentHashMap.compute(1,(k,v)->{
            return v==null?"原来没有值":v+"newValue";
        });
        // 处理 旧值新值
        concurrentHashMap.merge(1,"新值",(existValue,newValuex)->{
            return existValue+newValuex;
        });
        concurrentHashMap.merge(1,"新值",String::concat);
        System.out.println(concurrentHashMap.get(1));


        //////////// 线程安全的更新 方式 3
        ConcurrentHashMap<Integer, LongAdder> concurrentHashMap3 = new ConcurrentHashMap<>();
        // 直接使用线程安全的对象做 value
        // 若果 键对应值存在,则返回值,不存在则插入新值并返回 null 值
        concurrentHashMap3.putIfAbsent(1,new LongAdder());
        concurrentHashMap3.get(1).increment();
批操作 HashMap

        ConcurrentHashMap<Integer,Integer> concurrentHashMap = new ConcurrentHashMap();


        for (int i = 8; i < 15; i++) {
            concurrentHashMap.put(i,i);
        }

        // 对每个条目都做lambda表达式的操作,并返回第一个 非 null 的结果,如果所有结果都为 null 则返回 null
        // 参数:parallelismThreshold,映射元素超过阀值,就会并行的完成批操作,
        // 因此如果希望尽可能的多的线程运行,使用 1 作为阈值,希望单线程运行,使用 Long.MAX_VALUE 作为阈值
        Integer result=concurrentHashMap.search(1,(k, v)->{
            return v > 9 ? v*10 : null;
        });

        System.out.println(result); // 输出 100 120 140 ··· ,HashMap 的键没有顺序,而且多线程运行的话不一定是哪个线程先找到

        // forEach 有一个 不需要传入 线程阈值 的版本
        concurrentHashMap.forEach((k,v)-> System.out.println(v));
        concurrentHashMap.forEach(1L,(k,v)-> System.out.println(v));
        // 下面这个操作会报错  java.lang.UnsupportedOperationException ,这个是不能修改的
        // concurrentHashMap.forEachEntry(1,entry->entry.setValue(10));

        // forEach 第三个版本,可以在第 2 个参数传入一个转换器,然后将转换的结果交给第 3 个参数处理,转换器返回 null 的时候会跳过
        concurrentHashMap.forEach(1L,(k,v)-> v>10?v*10:null,System.out::println);


        //
        Integer reduce = concurrentHashMap.reduce(1L, (k,v)-> v > 10 ? v* 10 : null,Integer::sum);
        System.out.println(reduce); // 500

        // 可以把输出结果转为其他类型,这里第三个参数 basis ,必须写累加器的 零元素(描述是说什么在将返回类型装为long的时候是通过于默认值累加,多线程也会影响结果,所以必须用 0 作为 零元素,否则结果可能不受控制)
        long sum=concurrentHashMap.reduceToLong(1L,(k,v)-> v > 10 ? v* 10L : 0L,0L,Long::sum);
        System.out.println(sum); //500

        // 三种操作都还有只操作 key value entry 的版本
        //concurrentHashMap.searchKeys();
        //concurrentHashMap.searchValues();
        //concurrentHashMap.searchEntries();

        //concurrentHashMap.forEachKey();
        //concurrentHashMap.forEachValue();
        //concurrentHashMap.forEachEntry();

        //concurrentHashMap.reduceKeys();
        //concurrentHashMap.reduceValues();
        //concurrentHashMap.reduceEntries();
通过拷贝数组来写入的 CopyOnWriteArrayList 和 CopyOnWriteArraySet

这两个类在读取的时候,不加锁,获取底层数据数组的指针(类似 C 的指针概念)来读

在写入的时候,加锁,先将底层的数据数组拷贝,在拷贝的数组上进行修改,修改结束后直接将数据数组的指针替换成这个拷贝的数组指针

适合经常读很少写的集合,且由于读不加锁,拿指针来读,所以在存在写入的后,仍指向旧数组,使用的是旧的但是能保持一致性的数据

并行的数组算法

Arrays 提供了一些方法,可以并行的对数组做一些操作


        int[] ints={1,2,3,4};
        // 用数组每个元素的前一个元素值 和当前元素的值做运算,结果赋给当前下标元素,(第一个元素跳过)
        // x 为前一个元素值,y 为当前元素值
        // 需要满足 (a op b) op c == a op (b op c) 才可能实现并行运算
        Arrays.parallelPrefix(ints,(x,y)->x+y);
        System.out.println(Arrays.toString(ints)); // [1, 3, 6, 10]

        int[] nums={5,3,1,4,8,6};
        // 并行排序
        Arrays.parallelSort(nums);
        System.out.println(Arrays.toString(nums)); // [1, 3, 4, 5, 6, 8]


        int[] nums2=new int[20];
        // 并行设置值
        // i 为数组下标  源码段:for (int i = 0; i < array.length; i++)  array[i] = generator.applyAsInt(i);
        Arrays.setAll(nums2, i->i%6);
        System.out.println(Arrays.toString(nums2)); // [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 0, 1]
较早线程安全集合

        // 线程安全的动态数组
        Vector<Integer> vector=new Vector<>();
        // 线程安全的散列表
        Hashtable<Integer,Integer> hashtable=new Hashtable<>();

        // 通过 Collections提供的方法,包装出一个线程安全的集合
        List<Object> synchronizedList = Collections.synchronizedList(new ArrayList<>());
        // Collections.synchronizedMap();
        // Collections.synchronizedSet();
        // ······

相关文章

  • 十二、线程安全的集合

    十二、线程安全的集合 除了使用锁来保护共享数据结构,也可以直接使用一些实现了线程安全的对象 常见的线程安全集合 线...

  • 集合的线程安全

    线程安全的集合:Vector、HashTable线程不安全的集合:ArrayList、LinkedList、Has...

  • 常用集合及源码解读

    在工作中集合的使用非常广泛,集合是分线程安全和线程不安全的。在多线程环境下,是需要使用线程安全的集合的,否则就会...

  • ConcurrentHashMap源码设计分析

    二、线程安全(Thread-safe)的集合对象:● Vector 线程安全● HashTable 线程安全● S...

  • Java 集合

    1、java集合分类 线程安全的集合对象:Vector :是ArrayList的线程安全的实现HashTableS...

  • 线程安全集合

    早期线程安全的集合 我们先从早期的线程安全的集合说起,它们是Vector和HashTable VectorVect...

  • 「Java面试必会」谈谈并发包java .util.concur

    说到集合,就知道有的集合类并不是线程安全的,那Java中怎么保证集合是线程安全的? java .util.conc...

  • 并发总结(下)

    7.3.7 线程安全集合类概述 重点介绍 java.util.concurrent.* 下的线程安全集合类,可以发...

  • 关于Java中的 CopyOnWriteArraySet

    最近做的课程设计中用到了Java线程安全的集合类,因此关于对Java线程安全集合类的理解做个简单记录。Java集合...

  • Vector与ArrayList,HashMap与HashTab

    开发中哪些地方遇到线程安全的: 集合有些是线程安全的有些是线程不安全的。 Vector与ArrayList 区别:...

网友评论

      本文标题:十二、线程安全的集合

      本文链接:https://www.haomeiwen.com/subject/vmrqkktx.html