zoukankan      html  css  js  c++  java
  • 同步容器与并发容器

    同步容器

    性能差,线程不安全

    Vector (线程安全)--> ArrayList(线程不安全)

    Vector 

        public synchronized boolean add(E e) {
            modCount++;
            ensureCapacityHelper(elementCount + 1);
            elementData[elementCount++] = e;
            return true;
        }

    直接在方法上加同步,保证线程安全,不适用并发执行,性能损耗大

    arrayList

    1     public boolean add(E e) {
    2         ensureCapacityInternal(size + 1);  // Increments modCount!!
    3         elementData[size++] = e;
    4         return true;
    5     }

    ArrayList<String> s = new ArrayList<>();

    Collections.synchronizedList(s); 

    保证线程安全

    static class SynchronizedList<E>
    extends SynchronizedCollection<E>
    implements List<E>

    add()
    1         public void add(int index, E element) {
    2             synchronized (mutex) {list.add(index, element);}
    3         }

    锁的级别由方法级别到代码块级别。保证了线程了安全性,但两者性能较差

    HashTable(线程安全) --> HashMap(线程不安全)

    HashTable

    1     public synchronized V put(K key, V value) {
    2         // Make sure the value is not null
    3         if (value == null) {
    4             throw new NullPointerException();
    5         }

    put()方法加synchronized()

    HashMap

    put()

        public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }
    HashMap<String,Object> res = new HashMap<>();
    Collections.synchronizedMap(res);

    相当于内部代理
    1         public V put(K key, V value) {
    2             synchronized (mutex) {return m.put(key, value);}
    3         }

    并发容器

    ArrayList --> CopyOnWriteArrayList

    适用于大量读操作

    add()

     1     public boolean add(E e) {
     2         final ReentrantLock lock = this.lock;
     3         lock.lock();
     4         try {
     5             Object[] elements = getArray();
     6             int len = elements.length;
     7             Object[] newElements = Arrays.copyOf(elements, len + 1);
     8             newElements[len] = e;
     9             setArray(newElements);
    10             return true;
    11         } finally {
    12             lock.unlock();
    13         }
    14     }

    ReentranLock 获得锁,独占锁其他写线程无法进入。

    getArray()获取当前数组

    创建新数组,把旧数组长度拷贝 ,并加一

    把添加的元素放到新数组中,setArray(),让a 指向 array

        final void setArray(Object[] a) {
            array = a;
        }

    rerutn true ; 释放锁

    ConcurentLinkedQueue

    public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
    implements Queue<E>, java.io.Serializable


    offer()
     1     public boolean offer(E e) {
     2         checkNotNull(e);
     3         final Node<E> newNode = new Node<E>(e);
     4 
     5         for (Node<E> t = tail, p = t;;) {
     6             Node<E> q = p.next;
     7             if (q == null) {
     8                 // p is last node
     9                 if (p.casNext(null, newNode)) {
    10                     // Successful CAS is the linearization point
    11                     // for e to become an element of this queue,
    12                     // and for newNode to become "live".
    13                     if (p != t) // hop two nodes at a time
    14                         casTail(t, newNode);  // Failure is OK.
    15                     return true;
    16                 }
    17                 // Lost CAS race to another thread; re-read next
    18             }
    19             else if (p == q)
    20                 // We have fallen off list.  If tail is unchanged, it
    21                 // will also be off-list, in which case we need to
    22                 // jump to head, from which all live nodes are always
    23                 // reachable.  Else the new tail is a better bet.
    24                 p = (t != (t = tail)) ? t : head;
    25             else
    26                 // Check for tail updates after two hops.
    27                 p = (p != t && t != (t = tail)) ? t : q;
    28         }
    29     }

    1

    2

     3

    poll() 

     1     public E poll() {
     2         restartFromHead:
     3         for (;;) {
     4             for (Node<E> h = head, p = h, q;;) {
     5                 E item = p.item;
     6 
     7                 if (item != null && p.casItem(item, null)) {
     8                     // Successful CAS is the linearization point
     9                     // for item to be removed from this queue.
    10                     if (p != h) // hop two nodes at a time
    11                         updateHead(h, ((q = p.next) != null) ? q : p);
    12                     return item;
    13                 }
    14                 else if ((q = p.next) == null) {
    15                     updateHead(h, p);
    16                     return null;
    17                 }
    18                 else if (p == q)
    19                     continue restartFromHead;
    20                 else
    21                     p = q;
    22             }
    23         }
    24     }

     1

    2

    ConcurentHashMap

    HashMap本身不是线程安全的,因此不能用于多线程并发,因为多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,但是java中提供了一个ConcurrentHashMap,这个map是线程安全的,通过对get操作不加锁,对put操作分块加锁的方式来保证线程安全的同时还能有较高的读写性能

    JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树。

    1.7

    segment(分段锁)

    ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表,同时又是一个ReentrantLock(Segment继承了ReentrantLock)。


    ConcurrentHashMap使用分段锁技术,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。

    ConcurrentHashMap当中每个Segment各自持有一把锁。在保证线程安全的同时降低了锁的粒度,让并发操作效率更高

    HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。

    static final class HashEntry<K,V> { 
        final K key; 
        final int hash; 
        volatile V value; 
        final HashEntry<K,V> next; 
    } 

     HashEntry的一个特点,除了value以外,其他的几个变量都是final的,这意味着不能从hash链的中间或尾部添加或删除节点,因为这需要修改next 引用值,所有的节点的修改只能从头部开始。对于put操作,可以一律添加到Hash链的头部。但是对于remove操作,可能需要从中间删除一个节点,这就需要将要删除节点的前面所有节点整个复制一遍,最后一个节点指向要删除结点的下一个结点。这在讲解删除操作时还会详述。为了确保读操作能够看到最新的值,将value设置成volatile,这避免了加锁。

    1.8

    ConcurrentHashMap中包含一个table数组,其类型是一个Node数组;而Node是一个继承自Map.Entry<K, V>的链表,而当这个链表结构中的数据大于8,则将数据结构升级为TreeBin类型的红黑树结构。

    1. table:默认为null,初始化发生在第一次插入操作,默认大小为16的数组,用来存储Node节点数据,扩容时大小总是2的幂次方。

    2. nextTable:默认为null,扩容时新生成的数组,其大小为原数组的两倍。

    3. sizeCtl :默认为0,用来控制table的初始化和扩容操作,具体应用在后续会体现出来。

      • -1 代表table正在初始化

      • -N 表示有N-1个线程正在进行扩容操作

      • 其余情况:

        • 1、如果table未初始化,表示table需要初始化的大小。

        • 2、如果table初始化完成,表示table的容量,默认是table大小的0.75倍,居然用这个公式算0.75(n - (n >>> 2))。

    4. Node:保存key,value及key的hash值的数据结构。

    5. ForwardingNode:一个特殊的Node节点,hash值为-1,其中存储nextTable的引用。只有table发生扩容的时候,ForwardingNode才会发挥作用,作为一个占位符放在table中表示当前节点为null或则已经被移动。

    table 扩容

    当table容量不足的时候,即table的元素数量达到容量阈值sizeCtl,需要对table进行扩容。

    整个扩容分为两部分:

    1. 构建一个nextTable,大小为table的两倍。

    2. 把table的数据复制到nextTable中。

  • 相关阅读:
    django之路由层(反向解析)总结
    django 路由层(反向解析)03
    Django 文件配置、pycharm及django连接数据库、表的增删改查 总结
    Django 之 文件配置、pycharm及django连接数据库、创表及表的增删改查02
    Django框架简介(1)
    编程语言类别;运行Python程序的方式;变量和常量;Python程序的垃圾回收机制;
    Python语法之垃圾回收机制
    前端js之BOM和DOM操作
    前端js之JQuery
    获取当前应用信息
  • 原文地址:https://www.cnblogs.com/quyangyang/p/11209099.html
Copyright © 2011-2022 走看看