• java多线程基本概述(二十六)——免锁容器


    容器是所有编程中的基础工具,这其中也包括并发编程。出于这个原因,像Vector和Hashtable这类早期容器具有许多synchronized方法,当他们用于非多线程的应用程序中时,便会导致不可接受的开销。在Java1.2中,新的容器类库是不同步的,并且Collections类提供了各种static的同步的装饰方法,从而来同步不同类型的容器。尽管这是一种改进,因为它使你可以选择在你的容器中是否要使用同步,但是这种开销仍旧是基于synchronized加锁机制的。Java SE5特别添加了新容器,通过使用更灵巧的技术消除加锁,从而提高线程的安全的性能。

    这些免锁容器背后的通用策略是:对容器的修改可以与读取操作同时发生,只要读取者只能看到完成修改后的结果即可。修改是在容器数据结构的某一部分的一个单独的副本(有时是整个数据结构的副本)上执行的,并且这个副本在修改过程中是不可视的。只有当修改完成时,被修改的结构才会自动地与主数据结构进行交换,之后读取者就可以看到这个修改了。

    在CopyOnWriteArrayList中,写入将导致创建整个底层数组的副本,而源数组将保留在原地,使得复制的数组在被修改时,读取操作可以安全的执行。当修改完成时,一个原子性操作将把新的数组换入,使得新的读取操作可以看到这个新的修改。CopyOnWriteArrayList的好处之一是当多个迭代器同时遍历和修改这个列表时,不会抛出ConcurrentModificationException,因此你不必编写特殊的代码去防范这种异常,就像你以前必须作的那样。

    迭代器上进行的元素更改操作(removeset 和 add)不受支持。这些方法将抛出 UnsupportedOperationException

    CopyOnWriteArraySet将使用CopyOnWriteArrayList来实现其免锁行为。

    ConcurrentHashMap和ConcurrentLinkedQueue使用了类似的技术,允许并发的读取和写入,但是容器中只有部分内容而不是整个容器可以被复制和修改。然而,任何修改在完成之前,读取者仍旧不能看到它们。ConcurrentHashMap不会抛出ConcurrentModificationException异常。

      CopyOnWriteArrayList适合用在“读多,写少”的“并发”应用中,换句话说,它适合使用在读操作远远大于写操作的场景里,比如缓存。它不存在“扩容”的概念,每次写操作(add or remove)都要copy一个副本,在副本的基础上修改后改变array引用,所以称为“CopyOnWrite”,因此在写操作是加锁,并且对整个list的copy操作时相当耗时的,过多的写操作不推荐使用该存储结构。

    一个ConcurrentHashMap由多个segment组成,每一个segment都包含了一个HashEntry数组的hashtable, 每一个segment包含了对自己的hashtable的操作,比如get,put,replace等操作,这些操作发生的时候,对自己的hashtable进行锁定。由于每一个segment写操作只锁定自己的hashtable,所以可能存在多个线程同时写的情况,性能无疑好于只有一个hashtable锁定的情况。

    源码分析 在ConcurrentHashMap的remove,put操作还是比较简单的,都是将remove或者put操作交给key所对应的segment去做的,所以当几个操作不在同一个segment的时候就可以并发的进行。

    public V remove(Object key) {
        int hash = hash(key.hashCode());
            return segmentFor(hash).remove(key, hash, null);
        }

    而segment中的remove操作除了加锁之外和HashMap中的remove操作基本无异

       V remove(Object key, int hash, Object value) {
                lock();
                try {
                    int c = count - 1;
                    HashEntry<K,V>[] tab = table;
                    int index = hash & (tab.length - 1);
                    HashEntry<K,V> first = tab[index];
                    HashEntry<K,V> e = first;
                    while (e != null && (e.hash != hash || !key.equals(e.key)))
                        e = e.next;
     
                    V oldValue = null;
                    if (e != null) {
                        V v = e.value;
                        if (value == null || value.equals(v)) {
                            oldValue = v;
                            // All entries following removed node can stay
                            // in list, but all preceding ones need to be
                            // cloned.
                            ++modCount;
                            HashEntry<K,V> newFirst = e.next;
                            for (HashEntry<K,V> p = first; p != e; p = p.next)
                                newFirst = new HashEntry<K,V>(p.key, p.hash,
                                                              newFirst, p.value);
                            tab[index] = newFirst;
                            count = c; // write-volatile
                        }
                    }
                    return oldValue;
                } finally {
                    unlock();
                }
            }
    package tij;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * Created by huaox on 2017/4/21.
     *
     */
    public class CopyOnWriteArrayListDemo {
    
        private static class ReadTask implements Runnable {
            List<String> list;
    
             ReadTask(List<String> list) {
                this.list = list;
            }
    
            public void run() {
                for (String str : list) {
                    System.out.println(str);
                }
            }
        }
    
        private static class WriteTask implements Runnable {
            List<String> list;
            int index;
    
            WriteTask(List<String> list, int index) {
                this.list = list;
                this.index = index;
            }
    
            public void run() {
                list.remove(index);
                list.add(index, "write_" + index);
            }
        }
    
        public void run() {
            final int NUM = 5;
            List<String> list = new ArrayList<String>();
            for (int i = 0; i < NUM; i++) {
                list.add("main_" + i);
            }
            ExecutorService executorService = Executors.newFixedThreadPool(NUM);
            for (int i = 0; i < NUM; i++) {
                executorService.execute(new ReadTask(list));
                executorService.execute(new WriteTask(list, i));
            }
            executorService.shutdown();
        }
    
        public static void main(String[] args) {
            new CopyOnWriteArrayListDemo().run();
        }
    }

    输出结果:

        Exception in thread "pool-1-thread-3" java.util.ConcurrentModificationException
    main_0
    main_1
        at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    main_2
    main_3
        at java.util.ArrayList$Itr.next(ArrayList.java:851)
    main_4
    main_0
    main_1
    write_2
    main_3
    main_4
    main_0
    main_1
    write_2
    write_3
    main_4
    main_0
    write_0
    write_1
        at tij.CopyOnWriteArrayListDemo$ReadTask.run(CopyOnWriteArrayListDemo.java:22)
    write_2
    write_3
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    write_4
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)
    
    Process finished with exit code 0

    免锁容器的源码为赋值底层数组,然后赋新值,改引用

    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后

     public void run() {
            final int NUM = 5;
            List<String> list = new CopyOnWriteArrayList<>();
            for (int i = 0; i < NUM; i++) {
                list.add("main_" + i);
            }
            ExecutorService executorService = Executors.newFixedThreadPool(NUM);
            for (int i = 0; i < NUM; i++) {
                executorService.execute(new ReadTask(list));
                executorService.execute(new WriteTask(list, i));
            }
            executorService.shutdown();
        }

    输出结果:

    main_0
    main_1
    main_2
    main_3
    main_4
    main_0
    main_1
    write_2
    main_3
    main_4
    write_0
    main_1
    write_2
    write_3
    main_4
    write_0
    main_1
    write_2
    write_3
    write_4
    write_0
    write_1
    write_2
    write_3
    write_4
    
    Process finished with exit code 0
  • 相关阅读:
    Magento前台获取属性(自主获取)
    Magento PayPal提示订单地址无效解决方案
    Magento导入产品图片名称问题处理
    50个提升PHP性能的方法
    PHP开发者常犯的10个MySQL错误
    magento中给form添加输入验证
    PHP执行时间计算
    获得Magento全部Api方法
    Android 开发环境更新方法
    Android 网络通信开源框架 Volley JAR包的生成(一)
  • 原文地址:https://www.cnblogs.com/soar-hu/p/6742694.html
Copyright © 2020-2023  润新知