Java Collection Autumn trick review

The difference between abstract classes and interfaces

Let's look at an abstract class

 * @auther draymonder
 */
public abstract class AbstractClassTest {
    private int Test1;

    public int Test2;

    public void test1() {
        return ;
    }

    protected void test2() {
        return ;
    }

    private void test3() {
        return ;
    }

    void test4() {
        return ;
    }

    public abstract void test5();

    protected abstract void test6();

    public static void test7() {
        return ;
    }
}

Let us look at the interface

/**
 * @auther draymonder
 */
public interface IntefaceTest {
    public int Test1 = 0;

    void test1();

    default void test2() {
        return ;
    }

    public static void test3() {
        return ;
    }
}

From this we can know

  1. No interface configuration mode
  2. The method must be abstract interface (in the JDK8case interfacecan be used defaultto achieve method)
  3. Interfaces in addition to static, final variable, can not have other variables
  4. Interface supports multiple inheritance

Java collections

ArrayList

The default size of the array is 10.

private static final int DEFAULT_CAPACITY = 10;

When you add an element using ensureCapacityInternal()methods to ensure sufficient capacity, if not enough, you need to use grow()methods of expansion, the capacity of the new size oldCapacity + (oldCapacity >> 1), which is 1.5 times the capacity of the old.

Vector

The default size of the array is 10.

Vector requests each expansion space twice the size, and 1.5 times ArrayList.

Vector is synchronized, so the overhead is larger than ArrayList, access speed is slower. ArrayList preferably used instead Vector, because the synchronization operation can be controlled by the programmer;
may be used a method of synchronization list collections

List<String> list = new ArrayList<>();
List<String> synList = Collections.synchronizedList(list);

CopyOnWriteArrayList

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

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

Applicable scene

CopyOnWriteArrayList write operation at the same time allows the read operation, greatly improves the performance of read operations, so it is less suitable for reading and writing of scenarios.

But CopyOnWriteArrayList has its drawbacks:

  • Memory used: need to copy a new array during a write operation, so that the memory footprint is about twice the original;
  • Inconsistent data: the read operation can not read the real-time data, as part of the data write operation has not been synchronized to the reading group.
    So CopyOnWriteArrayList not suitable for memory-intensive and demanding real-time scene.

hashMap

hash power of 2 to act

  1. key & (hash - 1)Equivalent key % hash, but the former is more efficient than the latter
  2. When expansion, table capbecomes 2 * table cap, rehash just need to determine key & hashif it is 0, or the original table[old], otherwise it istable[old+table cap]

    Role mask code

    First consider how to mask a number of requirements for 10010000, 11111111 mask it can be obtained using the following method:
mask |= mask >> 1    11011000
mask |= mask >> 2    11111110
mask |= mask >> 4    11111111

mask + 1 is larger than the original number of the smallest n-th power of 2.

num     10010000
mask+1 100000000

The following is the code array capacity is calculated HashMap:

static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

List circulating at versions JDK7

In the expansion, because of a first interpolation method, therefore, it turned out to be A-> B, but a multithreaded happens.
A thread 1 has just come up, and back to the ready rehash B, but B-> A case where the presence of not yet released, so just appeared in the case A-> B-> A of
Circulation list
ZGmqsI.png

Why hashmap load factor of 0.75

If the load factor is too small, space utilization is too low; if the load factor is too large, then the hash collision will be more

JDK8 under hashmap Why is converted to a length of 8 list red-black tree

We look hashmap comments

Because TreeNodes are about twice the size of regular nodes, we
use them only when bins contain enough nodes to warrant use
(see TREEIFY_THRESHOLD). And when they become too small (due to
removal or resizing) they are converted back to plain bins.  In
usages with well-distributed user hashCodes, tree bins are
rarely used.  Ideally, under random hashCodes, the frequency of
nodes in bins follows a Poisson distribution
(http://en.wikipedia.org/wiki/Poisson_distribution) with a
parameter of about 0.5 on average for the default resizing
threshold of 0.75, although with a large variance because of
resizing granularity. Ignoring variance, the expected
occurrences of list size k are (exp(-0.5) * pow(0.5, k) /
factorial(k)). The first values are:

0:    0.60653066
1:    0.30326533
2:    0.07581633
3:    0.01263606
4:    0.00157952
5:    0.00015795
6:    0.00001316
7:    0.00000094
8:    0.00000006
more: less than 1 in ten million

We translate it to the proper way translation

因为树节点的大小大约是普通节点的两倍,所以我们
只有当容器中包含足够的节点以保证使用时才使用它们
(见TREEIFY_THRESHOLD)。当它们变得太小的时候
移除或调整大小)它们被转换回普通的箱子。在
使用分布良好的用户哈希码,树箱是
很少使用。理想情况下,在随机哈希码下
箱中的节点遵循泊松分布
(http://en.wikipedia.org/wiki/Poisson_distribution)
默认大小调整的参数平均约为0.5
阈值为0.75,虽然由于方差较大
调整粒度。忽略方差,得到期望
列表大小k的出现次数为(exp(-0.5) * pow(0.5, k) /
阶乘(k))。第一个值是:
0:0.60653066
1:0.30326533
2:0.07581633
3:0.01263606
4:0.00157952
5:0.00015795
6:0.00001316
7:0.00000094
8:0.00000006
多于:少于千分之一

Therefore, the node is inserted follow a Poisson distribution , resulting in a barrel to eight nodes is extremely small probability event, so this is the case we can use the red-black tree speed up getoperations

ConcurrentHashMap

Does not support the key support for the null value is null I do not know

Under the version JDK7

//默认的数组大小16(HashMap里的那个数组)
static final int DEFAULT_INITIAL_CAPACITY = 16;

//扩容因子0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
 
//ConcurrentHashMap中的数组
final Segment<K,V>[] segments

//默认并发标准16
static final int DEFAULT_CONCURRENCY_LEVEL = 16;

//Segment是ReentrantLock子类,因此拥有锁的操作
 static final class Segment<K,V> extends ReentrantLock implements Serializable {
  //HashMap的那一套,分别是数组、键值对数量、阈值、负载因子
  transient volatile HashEntry<K,V>[] table;
  transient int count;
  transient int threshold;
  final float loadFactor;

  Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
            this.loadFactor = lf;
            this.threshold = threshold;
            this.table = tab;
        }
 }
 
 //换了马甲还是认识你!!!HashEntry对象,存key、value、hash值以及下一个节点
 static final class HashEntry<K,V> {
        final int hash;
        final K key;
        volatile V value;
        volatile HashEntry<K,V> next;
 }
//segment中HashEntry[]数组最小长度
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;

//用于定位在segments数组中的位置,下面介绍
final int segmentMask;
final int segmentShift;

put function

public V put(K key, V value) {
    Segment<K,V> s;
    //步骤①注意valus不能为空!!!
    if (value == null)
        throw new NullPointerException();
    //根据key计算hash值,key也不能为null,否则hash(key)报空指针
    int hash = hash(key);
    //步骤②派上用场了,根据hash值计算在segments数组中的位置
    int j = (hash >>> segmentShift) & segmentMask;
    //步骤③查看当前数组中指定位置Segment是否为空
    //若为空,先创建初始化Segment再put值,不为空,直接put值。
    if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
         (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
        s = ensureSegment(j);
    return s.put(key, hash, value, false);
}

ensureSegement

JDK7 can see the next version ConcurrentHashMapof segmentis to use 写时复制, and use CASalgorithms to replace copies

private Segment<K,V> ensureSegment(int k) {
    //获取segments
    final Segment<K,V>[] ss = this.segments;
    long u = (k << SSHIFT) + SBASE; // raw offset
    Segment<K,V> seg;
    if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
        //拷贝一份和segment 0一样的segment
        Segment<K,V> proto = ss[0]; // use segment 0 as prototype
        //大小和segment 0一致,为2
        int cap = proto.table.length;
        //负载因子和segment 0一致,为0.75
        float lf = proto.loadFactor;
        //阈值和segment 0一致,为1
        int threshold = (int)(cap * lf);
        //根据大小创建HashEntry数组tab
        HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
        //再次检查
        if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
            == null) { // recheck
            根据已有属性创建指定位置的Segment
            Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
            while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                   == null) {
                if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
                    break;
            }
        }
    }
    return seg;
}

put value

Acquiring first lock tab [hash (key)]
and operate

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    //步骤① start
    HashEntry<K,V> node = tryLock() ? null :
        scanAndLockForPut(key, hash, value);
    //步骤① end
    V oldValue;
    try {
        //步骤② start
        //获取Segment中的HashEntry[]
        HashEntry<K,V>[] tab = table;
        //算出在HashEntry[]中的位置
        int index = (tab.length - 1) & hash;
        //找到HashEntry[]中的指定位置的第一个节点
        HashEntry<K,V> first = entryAt(tab, index);
        for (HashEntry<K,V> e = first;;) {
            //如果不为空,遍历这条链
            if (e != null) {
                K k;
                //情况① 之前已存过,则替换原值
                if ((k = e.key) == key ||
                    (e.hash == hash && key.equals(k))) {
                    oldValue = e.value;
                    if (!onlyIfAbsent) {
                        e.value = value;
                        ++modCount;
                    }
                    break;
                }
                e = e.next;
            }
            else {
                //情况② 另一个线程的准备工作
                if (node != null)
                    //链表头插入方式
                    node.setNext(first);
                else //情况③ 该位置为空,则新建一个节点(注意这里采用链表头插入方式)
                    node = new HashEntry<K,V>(hash, key, value, first);
                //键值对数量+1
                int c = count + 1;
                //如果键值对数量超过阈值
                if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                    //扩容
                    rehash(node);
                else //未超过阈值,直接放在指定位置
                    setEntryAt(tab, index, node);
                ++modCount;
                count = c;
                //插入成功返回null
                oldValue = null;
                break;
            }
        }
    //步骤② end
    } finally {
        //步骤③
        //解锁
        unlock();
    }
    //修改成功,返回原值
    return oldValue;
}

scanAndLockForPut

First retries64 times, no words, only with ReentrantLockreentrant lock

private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
    //通过Segment和hash值寻找匹配的HashEntry
    HashEntry<K,V> first = entryForHash(this, hash);
    HashEntry<K,V> e = first;
    HashEntry<K,V> node = null;
    //重试次数
    int retries = -1; // negative while locating node
    //循环尝试获取锁
    while (!tryLock()) {
        HashEntry<K,V> f; // to recheck first below
        //步骤①
        if (retries < 0) {
            //情况① 没找到,之前表中不存在
            if (e == null) {
                if (node == null) // speculatively create node
                    //新建 HashEntry 备用,retries改成0
                    node = new HashEntry<K,V>(hash, key, value, null);
                retries = 0;
            }
            //情况② 找到,刚好第一个节点就是,retries改成0
            else if (key.equals(e.key))
                retries = 0;
            //情况③ 第一个节点不是,移到下一个,retries还是-1,继续找
            else
                e = e.next;
        }
        //步骤②
        //尝试了MAX_SCAN_RETRIES次还没拿到锁,简直B了dog!
        else if (++retries > MAX_SCAN_RETRIES) {
            //泉水挂机
            lock();
            break;
        }
        //步骤③
        //在MAX_SCAN_RETRIES次过程中,key对应的entry发生了变化,则从头开始
        else if ((retries & 1) == 0 &&
                 (f = entryForHash(this, hash)) != first) {
            e = first = f; // re-traverse if entry changed
            retries = -1;
        }
    }
    return node;
}

Last put processes

rehashIn the words of the same version jdk8rehash
put the process under ConcurrentHashMap JDK7

size

retries2 If you still different, then reentranLockwaiting their turns unlockto calculate the size of each tab

ConcurrentHashMap JDK8 under

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) {
    // key/value不能为空!!!
    if (key == null || value == null) throw new NullPointerException();
    //计算hash值
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        //注释① 表为null则初始化
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        //CAS方法判断指定位置是否为null,为空则通过创建新节点,通过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
        }
        //当前节点正在扩容
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        //指定位置不为空
        else {
            V oldVal = null;
            //注释② 加锁
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    //节点是链表的情况
                    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;
                            }
                        }
                    }
                    //节点是红黑树的情况
                    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;
                        }
                    }
                    else if (f instanceof ReservationNode)
                        throw new IllegalStateException("Recursive update");
                }
            }
            if (binCount != 0) {
                //链表中节点个数超过8转成红黑树
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    //注释③ 添加节点
    addCount(1L, binCount);
    return null;
}

Why tab [hash (key)] with cas, but put elements inside it need to use synchronized

In fact, the probability of hash collision is very low, so the number of synchronized call is not much more there is in the cas ...
then that is synchronized cas than advantages ...

size

Every putcompleted, will call the addCountmethod

private final void addCount(long x, int check) {
    CounterCell[] as; long b, s;
    if ((as = counterCells) != null ||
        !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
        CounterCell a; long v; int m;
        boolean uncontended = true;
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
            !(uncontended =
              U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
            fullAddCount(x, uncontended);
            return;
        }
        if (check <= 1)
            return;
        s = sumCount();
    }
    if (check >= 0) {
        Node<K,V>[] tab, nt; int n, sc;
        while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
               (n = tab.length) < MAXIMUM_CAPACITY) {
            int rs = resizeStamp(n);
            if (sc < 0) {
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                    transferIndex <= 0)
                    break;
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                    transfer(tab, nt);
            }
            else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                         (rs << RESIZE_STAMP_SHIFT) + 2))
                transfer(tab, null);
            s = sumCount();
        }
    }
}

Guess you like

Origin www.cnblogs.com/Draymonder/p/11114263.html