zoukankan      html  css  js  c++  java
  • Java并发容器类综述

    0) 出现原因:

       起初在对程序性能要求不高的年代,基本都是单线程编程,那个时候CPU基本是单核,随着计算机硬件技术的发展,出现了多核CPU,过去的单核编程已经不能更好的使用我们的CPU核心数目,加上对程序的速度性能的要求的提高,出现了并发编程技术. 单核程序的性能的瓶颈就是频繁的上下文切换,那么我们就可以通过减少上下文的切换来提高性能。让更多的CPU协同 异步工作.

    1)发展过程:

            Java在 Java5 添加的一个并发工具包。这个包包含了一系列能够让 Java 的并发编程变得更加简单轻松的类。在这之前,你需要自己手动去实现相关的工具类. 是以工具类的形式提供 包路径为  java.util.concurrent

            提供的类:

      • 阻塞队列 BlockingQueue
      • 数组阻塞队列 ArrayBlockingQueue
      • 延迟队列 DelayQueue
      • 链阻塞队列 LinkedBlockingQueue
      • 具有优先级的阻塞队列 PriorityBlockingQueue
      • 同步队列 SynchronousQueue
      • 阻塞双端队列 BlockingDeque
      • 链阻塞双端队列 LinkedBlockingDeque
      • 并发 Map ConcurrentMap
      • 并发导航映射 ConcurrentNavigableMap
      • 闭锁 ConutDownLatch
      • 栅栏 CyclicBarrier
      • 交换机 Exchanger
      • 信号量 Semaphore
      • 执行器服务 ExecutorService
      • 线程池执行者 ThreadPoolExecutor
      • 定时执行者服务 ScheduledExecutorService
      • 使用 ForkJoinPool 进行分叉和合并
      • 锁 Lock
      • 读写锁 ReadWriteLock
      • 原子性布尔 AtomicBoolean
      • 原子性整型 AtomicInteger
      • 原子性长整型 AtomicLong
      • 原子性引用型 AtomicReference

          我们选择常用的进行概述.

    2)必要理论知识

    悲观锁
    总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

    乐观锁
    总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

    CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试

    3)  目前最新发展:

               Java类库中出现的第一个关联的集合类是 Hashtable ,它是JDK 1.0的一部分。 Hashtable 提供了一种易于使用的、线程安全的、关联的map功能,这当然也是方便的。然而,线程安全性是凭代价换来的―― Hashtable 的所有方法都是同步的。 此时,无竞争的同步会导致可观的性能代价。 Hashtable 的后继者 HashMap 是作为JDK1.2中的集合框架的一部分出现的,它通过提供一个不同步的基类和一个同步的包装器 Collections.synchronizedMap ,解决了线程安全性问题.

              但是: 其结果是尽管表面上这些程序在负载较轻的时候能够正常工作,但是一旦负载较重,它们就会开始抛出 NullPointerExceptionConcurrentModificationException 异常

    随后出现了 ConcurrentMap

    ConcurrentMap 的实现

    concurrent 包里面就一个类实现了 ConcurrentMap 接口

    • ConcurrentHashMap
    ConcurrentHashMap

    ConcurrentHashMap 和 HashTable 类很相似,但 ConcurrentHashMap 能提供比 HashTable 更好的并发性能。在你从中读取对象的时候,ConcurrentHashMap 并不会把整个 Map 锁住。此外,在你向其写入对象的时候,ConcurrentHashMap 也不会锁住整个 Map,它的内部只是把 Map 中正在被写入的部分锁定。
    其实就是把 synchronized 同步整个方法改为了同步方法里面的部分代码。

    另外一个不同点是,在被遍历的时候,即使是 ConcurrentHashMap 被改动,它也不会抛 ConcurrentModificationException。尽管 Iterator 的设计不是为多个线程同时使用。


    使用例子:

    public class ConcurrentHashMapExample {
    
        public static void main(String[] args) {
    //        HashMap<String, String> map = new HashMap<>();
            ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
            map.put("1", "a");
            map.put("2", "b");
            map.put("3", "c");
            map.put("4", "d");
            map.put("5", "e");
            map.put("6", "f");
            map.put("7", "g");
            map.put("8", "h");
            new Thread1(map).start();
            new Thread2(map).start();
    
        }
    
    }
    
    class Thread1 extends Thread {
    
        private final Map map;
    
        Thread1(Map map) {
            this.map = map;
        }
    
        @Override
        public void run() {
            super.run();
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.remove("6");
        }
    }
    
    class Thread2 extends Thread {
    
        private final Map map;
    
        Thread2(Map map) {
            this.map = map;
        }
    
        @Override
        public void run() {
            super.run();
            Set set = map.keySet();
            for (Object next : set) {
                System.out.println(next + ":" + map.get(next));
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }复制代码

    CopyOnWriteArrayList

    在那些遍历操作大大地多于插入或移除操作的并发应用程序中,一般用 CopyOnWriteArrayList 类替代 ArrayList 。如果是用于存放一个侦听器(listener)列表,例如在AWT或Swing应用程序中,或者在常见的JavaBean中,那么这种情况很常见(相关的 CopyOnWriteArraySet 使用一个 CopyOnWriteArrayList 来实现 Set 接口) 。

    原理实现:

    如果您正在使用一个普通的 ArrayList 来存放一个侦听器列表,那么只要该列表是可变的,而且可能要被多个线程访问,您 就必须要么在对其进行迭代操作期间,要么在迭代前进行的克隆操作期间,锁定整个列表,这两种做法的开销都很大。当对列表执行会引起列表发生变化的操作时, CopyOnWriteArrayList 并不是为列表创建一个全新的副本,它的迭代器肯定能够返回在迭代器被创建时列表的状态,而不会抛出 ConcurrentModificationException 。在对列表进行迭代之前不必克隆列表或者在迭代期间锁 定列表,因为迭代器所看到的列表的副本是不变的。换句话说, CopyOnWriteArrayList 含有对一个不可变数组的一个可变的引用,因此,只要保留好那个引用,您就可以获得不可变的线程安全性的好处,而且不用锁 定列表。


    4)目前最新应用情况:

    以ConcurrentHashMap为例子:

    jdk7版本

    ConcurrentHashMap和HashMap设计思路差不多,但是为支持并发操作,做了一定的改进,ConcurrentHashMap引入Segment 的概念,目的是将map拆分成多个Segment(默认16个)。操作ConcurrentHashMap细化到操作某一个Segment。在多线程环境下,不同线程操作不同的Segment,他们互不影响,这便可实现并发操作

    最新JDk8 实现:

    jdk8版本的ConcurrentHashMap相对于jdk7版本,发送了很大改动,jdk8直接抛弃了Segment的设计,采用了较为轻捷的Node + CAS + Synchronized设计,保证线程安全。

    807144-6264960638978dff.webp

    ConcurrentHashMap结构图

    看上图ConcurrentHashMap的大体结构,一个node数组,默认为16,可以自动扩展,扩展速度为0.75

    private static finalint DEFAULT_CONCURRENCY_LEVEL = 16;
    private static final float LOAD_FACTOR = 0.75f;
    
    

    每一个节点,挂载一个链表,当链表挂载数据大于8时,链表自动转换成红黑树

    static final int TREEIFY_THRESHOLD = 8;
    
    

    部分代码:

    public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
        implements ConcurrentMap<K,V>, Serializable {
        transient volatile Node<K,V>[] table;
    }
    
    

    static class Node<K,V> implements Map.Entry<K,V> {
            final int hash;
            final K key;
            volatile V val;
            volatile Node<K,V> next;
    }
    
    

    static final class TreeNode<K,V> extends Node<K,V> {
            TreeNode<K,V> parent;  // red-black tree links
            TreeNode<K,V> left;
            TreeNode<K,V> right;
            TreeNode<K,V> prev;    // needed to unlink next upon deletion
            boolean red;
    }
    
    

    static final class TreeBin<K,V> extends Node<K,V> {
            TreeNode<K,V> root;
            volatile TreeNode<K,V> first;
            volatile Thread waiter;
            volatile int lockState;
     }
  • 相关阅读:
    Tomcat自动部署
    java环境配置
    django-crispy-forms入门指南
    hibernate级联删除
    bzoj1659: [Usaco2006 Mar]Lights Out 关灯
    bzoj1658: [Usaco2006 Mar]Water Slides 滑水
    bzoj5470 / P4578 [FJOI2018]所罗门王的宝藏(差分约束)
    P2864 [USACO06JAN]树林The Grove
    bzoj1651 / P2859 [USACO06FEB]摊位预订Stall Reservations
    bzoj1647 / P1985 [USACO07OPEN]翻转棋
  • 原文地址:https://www.cnblogs.com/dgwblog/p/12744213.html
Copyright © 2011-2022 走看看