从Java并发集合看锁优化策略

时间:2022-07-24
本文章向大家介绍从Java并发集合看锁优化策略,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

1.ConcurrentHashMap

ConcurrentHashMap是支持并发的HashMap类,可以在多线程环境下保证线程安全。ConcurrentHashMap的核心思想就是减小锁的粒度。传统的线程安全的Hashtable在同步时采用了synchronized关键字,所有对Hashtable的读写操作都要先获取对象锁,在锁竞争比较激烈的情况下会导致性能很低。ConcurrentHashMap在此基础上采用和分段的策略,将一个HashMap最多分为16个段(Segment),对不同段上的操作可以并发执行,只有在同一个段上的读写才使用加锁的策略。通过这种减小锁粒度的方式,大大提高了性能。如ConcurrentHashMap的put方法源码如下:

public V put(K key, V value) {
        //先找到value所在的段
        Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key);
        int j = (hash >>> segmentShift) & segmentMask;
        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);
    }

ConcurrentHashMap.Segment的put方法如下:

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            HashEntry<K,V> node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                HashEntry<K,V>[] tab = table;
                int index = (tab.length - 1) & hash;
                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);
                        int c = count + 1;
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
                unlock();
            }
            return oldValue;
        }

可以看出,在对ConcurrentHashMap进行操作时,会先找到数据所在的段,只有在同一个段上的数据才会采用加锁的机制,非同段则可以并发执行。

2.CopyOnWriteArrayList

CopyOnWriteArrayList采用了读、写锁分离的思想。所有对CopyOnWriteArrayList的读操作可以进行无锁的并发执行,当进行写操作时,会先将原数组复制一份再进行修改,修改后将新数组的引用传递给原数组,并释放复制数组所占的空间。CopyOnWriteArrayList非常适合应用于大量读取少量修改的并发场景下。

CopyOnWriteArrayList的get方法如下:

public E get(int index) {
        //直接进行随机访问
        return get(getArray(), index);
    }

add方法实现如下:

 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();
        }
    }

综上,CopyOnWriteArrayList在读操作时可以进行无锁的并发,在写操作时才进行同步操作,在大量读少量写的场景下可以大大提高效率。