zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    Linux下sed,awk,grep,cut,find学习笔记
    Python文件处理(1)
    KMP详解
    Java引用详解
    解决安卓中页脚被输入法顶起的问题
    解决swfupload上传控件文件名中文乱码问题 三种方法 flash及最新版本11.8.800.168
    null id in entry (don't flush the Session after an exception occurs)
    HQL中的Like查询需要注意的地方
    spring mvc controller间跳转 重定向 传参
    node to traverse cannot be null!
  • 原文地址:https://www.cnblogs.com/soar-hu/p/6742694.html
Copyright © 2011-2022 走看看