Android并发编程 原子类与并发容器 (3)

p.casNext(null, newNode)方法用于将入队节点设置为当前队列尾节点的next节点,如果p是null,
表示p是当前队列的尾节点,如果不为null,表示有其他线程更新了尾节点,则需要重新获取当前队列的尾节点

3.更新尾节点
casTail(t, newNode);

将尾节点移到最后(即把tail指向新节点)
如果失败了,那么说明有其他线程已经把tail移动过,此时新节点newNode为尾节点,tail为其前驱结点

poll() public E poll() { // 设置起始点 restartFromHead: for (;;) { for (Node<E> h = head, p = h, q;;) { E item = p.item; // 利用cas 将第一个节点设置为null if (item != null && p.casItem(item, null)) { // 和上面类似,p的next被删了, // 然后然后判断一下,目的为了保证head的next不为空 if (p != h) // hop two nodes at a time updateHead(h, ((q = p.next) != null) ? q : p); return item; } else if ((q = p.next) == null) { // 有可能已经被另外线程先删除了下一个节点 // 那么需要先设定head 的位置,并返回null updateHead(h, p); return null; } else if (p == q) continue restartFromHead; else // 和offer 类似,保证下一个节点有值,才能删除 p = q; } } } ConcurrentHashMap(并发的HashMap)

JDK1.7与JDK1.8 ConcurrentHashMap的实现还是有不小的区别的

JDK1.7

在JDK1.7版本中,ConcurrentHashMap的数据结构是由一个Segment数组和多个HashEntry组成。

Segment数组的意义就是将一个大的table分割成多个小的table来进行加锁,也就是上面的提到的锁分离技术,而每一个Segment元素存储的是HashEntry数组+链表,这个和HashMap的数据存储结构一样.

Android并发编程 原子类与并发容器


Android并发编程 原子类与并发容器

JDK1.8

1.8中放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,结构如下:

Android并发编程 原子类与并发容器


Android并发编程 原子类与并发容器

put实现 public V put(K key, V value) { return putVal(key, value, false); } /** Implementation for put and putIfAbsent */ 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) //① 只有在执行第一次put方法时才会调用initTable()初始化Node数组 tab = initTable(); //② 如果相应位置的Node还未初始化,则通过CAS插入相应的数据; 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 empty bin } //③ 如果相应位置的Node不为空,且当前该节点处于移动状态 帮助转移数据 else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); //④ 如果相应位置的Node不为空,且当前该节点不处于移动状态,则对该节点加synchronized锁, else { V oldVal = null; synchronized (f) { if (tabAt(tab, i) == f) { //⑤ 如果该节点的hash不小于0,则遍历链表更新节点或插入新节点; if (fh >= 0) { binCount = 1; for (Node<K,V> e = f;; ++binCount) { K ek; if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { oldVal = e.val; if (!onlyIfAbsent) e.val = value; break; } Node<K,V> pred = e; if ((e = e.next) == null) { pred.next = new Node<K,V>(hash, key, value, null); break; } } } //⑥ 如果该节点是TreeBin类型的节点,说明是红黑树结构,则通过putTreeVal方法往红黑树中插入节点; else if (f instanceof TreeBin) { Node<K,V> p; binCount = 2; if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) { oldVal = p.val; if (!onlyIfAbsent) p.val = value; } } } } /** *如果binCount不为0,说明put操作对数据产生了影响,如果当前链表的个数达到8个, *通过treeifyBin方法转化为红黑树, *如果oldVal不为空,说明是一次更新操作,没有对元素个数产生影响,则直接返回旧值; */ if (binCount != 0) { if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } addCount(1L, binCount); return null; } CopyOnWriteArrayList(线程安全的ArrayList)

JDK1.8中关于CopyOnWriteArrayList的官方介绍如下

A thread-safe variant of {@link java.util.ArrayList} in which all mutative
operations ({@code add}, {@code set}, and so on)
are implemented bymaking a fresh copy of the underlying array.

中文翻译大致是

CopyOnWriteArrayList是一个线程安全的java.util.ArrayList的变体,
add,set等改变CopyOnWriteArrayList的操作是通过制作当前数据的副本实现的

其实意思很简单,假设有一个数组如下所示

Android并发编程 原子类与并发容器

并发读取

多个线程并发读取是没有任何问题的

Android并发编程 原子类与并发容器

更新数组

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdjfz.html