欢迎访问昆山宝鼎软件有限公司网站! 设为首页 | 网站地图 | XML | RSS订阅 | 宝鼎邮箱 | 宝鼎售后问题提交 | 后台管理


新闻资讯

MENU

软件开发知识
原文出处: saymagic

多线程编程一直是老生常谈的问题,在Java中,跟着JDK的逐渐成长,JDK提供应我们的并发模子也越来越多,本文摘取三例利用差异道理的模子,阐明其大抵道理。

COW之CopyOnWriteArrayList

cow是copy-on-write的简写,这种模子来历于linux系统fork呼吁,Java中一种利用cow模子来实现的并发类是CopyOnWriteArrayList。对比于Vector,它的读操纵是无需加锁的:

public E get(int index) {
     return (E) elements[index];
}

之所以有如此神奇功能,其采纳的是空间调换时间的要领,查察其add要领:

 public synchronized boolean add(E e) {
      Object[] newElements = new Object[elements.length + 1];
      System.arraycopy(elements, 0, newElements, 0, elements.length);
      newElements[elements.length] = e;
      elements = newElements;
      return true;
 }

我们留意到,CopyOnWriteArrayList的add要领是需要加锁的,但其内部并没有直接对elements数组做操纵,而是先copy一份当前的数据到一个新的数组,然后对新的数组举办赋值操纵。这样做就让get操纵从同步中摆脱出来。因为变动的数据并没有产生在get所需的数组中。而是放生在新生成的副本中,所以不需要同步。但应该留意的是,尽量如此,get操纵照旧大概会读取到脏数据的。

CopyOnWriteArrayList的另一特点是答允多线程遍历,且其它线程变动数据并不会导致遍历线程抛出ConcurrentModificationException 异常,来看下iterator()

public Iterator<E> iterator() {
     Object[] snapshot = elements;
     return new CowIterator<E>(snapshot, 0, snapshot.length);
}

这个CowIterator 是 ListIterator的子类,这个Iterator的特点是它并不支持对数据的变动操纵:

public void add(E object) {
     throw new UnsupportedOperationException();
}
public void remove() {
    throw new UnsupportedOperationException();
}
public void set(E object) {
    throw new UnsupportedOperationException();
}

这样做的原因也很容易领略,我们可以简朴地的认为CowIterator中的snapshot是不行变数组,因为list中有数据更新城市生成新数组,而不会改变snapshot, 所以此时Iterator没步伐再将变动的数据写回list了。同理,list数据有更新也不会反应在CowIterator中。CowIterator只是担保其迭代进程不会产生异常。

CAS之ConcurrentHashMap(JDK1.8)

CAS是Compare and Swap的简写,即较量与替换,CAS造作将较量和替换封装为一组原子操纵,不会被外部打断。这种原子操纵的担保往往由处理惩罚器层面提供支持。

在Java中有一个很是神奇的Unsafe类来对CAS提供语言层面的接口。但类如其名,此等神器假如利用不妥,会造成武功尽失的,所以Unsafe差池外开放,想利用的话需要通过反射等能力。这里差池其做展开。先容它的原因是因为它是JDK1.8中ConcurrentHashMap的实现基本。

ConcurrentHashMapHashMap对数据的存储有着相似的处所,都回收数组+链表+红黑树的方法。根基逻辑是内部利用Node来生存map中的一项key, value布局,对付hash不斗嘴的key,利用数组来生存Node数据,而每一项Node都是一个链表,用来生存hash斗嘴的Node,当链表的巨细到达必然水平会转为红黑树,这样会使在斗嘴数据较多时也会有较量好的查询效率。

相识了ConcurrentHashMap的存储布局后,我们来看下在这种布局下,ConcurrentHashMap是如何实现高效的并发操纵,这得益于ConcurrentHashMap中的如下三个函数。

static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
    return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                    Node<K,V> c, Node<K,V> v) {
    return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
    U.putOrderedObject(tab, ((long)i << ASHIFT) + ABASE, v);
}

个中的U就是我们前文提到的Unsafe的一个实例,这三个函数都通过Unsafe的几个要领担保了是原子性:

  • tabAt浸染是返回tab数组第i项
  • casTabAt函数是比拟tab第i项是否与c相等,相等的话将其配置为v。
  • setTabAt将tab的第i项配置为v
  • 有了这三个函数就可以担保ConcurrentHashMap的线程安详吗?并不是的,ConcurrentHashMap内部也利用较量多的synchronized,不外与HashTable这种对所有操纵都利用synchronized差异,ConcurrentHashMap只在特定的环境下利用synchronized,来较少锁的定的区域。来看下putVal要领(精简版):

    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                        break;                   // no lock when adding to embin
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                synchronized (f) {
                        ....
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }