• 多线程之同步类与并非容器


      一:为什么会出现同步容器?

        平时我们使用的ArrayList,HashSet,HashMap都是非线程安全的,如果有多个线程同时操作集合,就会出现线程安全问题。

            下面举个例子来说明一下,为什么是是非线程安全的。

    public class ArrayListDemo {
    
        public static void main(String[]args){
            final HashMap<String,String> map=new HashMap<String, String>();
            map.put("A", "A");
            Thread t1=new Thread(new Runnable() {
                public void run() {
                    map.put("A", "B");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"t1");
            Thread t2=new Thread(new Runnable() {
                public void run() {
                    System.out.println(map.get("A"));
                }
            },"t2");
            t1.start();
            t2.start();
        }
    }

      运行结果有时候为A,有时候为B,出现这种不确定的结果。所以在多线程环境下就需要对集合容器进行同步。

      二: Java中的同步容器主要有以下两种

      (1)Vector,HashTable,stack

      (2)Collections类中提供的静态工厂方法创建的类

      Vector和ArrayList类似,但是Vector里面的方法都加了同步synchronized,从而实现了线程安全。源码如下:

         

       而Collections主要是传入一个非线程安全的集合后,返回一个线程安全的集合。

      

      三:同步容器的缺陷

      同步容器的方法都使用了synchronized关键字进行同步,这必然会影响到性能问题。下面写个例子测试一下。

    public class VectorDemo {
    
        public static void main(String []args){
            
            ArrayList<Integer> list = new ArrayList<Integer>();
            long start = System.currentTimeMillis();
            for(int i=0;i<1000000;i++)
                list.add(i);
            for(int i=0;i<list.size();i++)
                list.get(i);
            long end = System.currentTimeMillis();
            System.out.println("ArrayList进行10000次插入与读取操作耗时:"+(end-start)+"ms");
            
            Vector<Integer> vector = new Vector<Integer>();
            start = System.currentTimeMillis();
            for(int i=0;i<1000000;i++)
                vector.add(i);
            for(int i=0;i<vector.size();i++)
                vector.get(i);
            end = System.currentTimeMillis();
            System.out.println("Vector进行10000次插入与读取操作耗时:"+(end-start)+"ms");
            
        }
    }

      显示结果如下:

      

      另外,由于vector中add方法与get方法都加了synchronized进行同步,所以在多线程情况,同一时间只有一个线程获得锁,其他线程只能等待,竞争同一把锁。

      四:并发容器之ConcurrentMap与CopyOnWrite

      ConcurrentMap代替HashTable。

      CopyOnWriteArrayList和CopyOnWriteArraySet分别代替List和Set。

      (1)ConcurrentMap

      HashTable容器使用了synchronized关键字来同步(get与put方法都使用了synchronized),在多线程情况下,效率比较十分低下,如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素。HashTable是根据散列值分段存储的,在同步的时候锁住了所有的段,而ConcurrentHashMap加锁的时候根据散列值锁住了散列值锁对应的那段,因此提高了并发性能。如下图所示:

      

      ConcurrentHashMap是一种细粒度的加锁方式,当线程同时访问一个数据的时候,才会进入阻塞状态。当线程访问不同数据块的数据时候,线程之间使用不同的锁对象,所以不会进入阻塞状态,这样大大提高了性能。

      (2)CopyOnWrite  

      CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

      查看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();
            }
        }
    
      public E get(int index) {
            return get(getArray(), index);
        }

      添加的时候加了锁,读取的时候不加锁,所以当多线程同时往集合中添加元素的时候,会读取到旧的数据,因为写的时候不会锁住旧的容器。

      五:CopyOnWrite的缺点:

      内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。

      数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。

       参考网址:http://blog.csdn.net/hechurui/article/details/49508473

            http://ifeve.com/java-copy-on-write/

            http://www.cnblogs.com/dolphin0520/p/3932905.html

  • 相关阅读:
    DAT批处理文件语法
    TreeView控件问题汇总
    windows xp home安装iis
    【Vegas原创】网站计数器(asp)
    转载:shell python脚本互调
    转载:linux的文件属性和权限学习——分析ls命令结果
    python 正则表达式匹配中文
    Python正则表达式
    linux命令备份
    移植算法编译环境到linux【redhat9.0如何显示汉字】
  • 原文地址:https://www.cnblogs.com/gdpuzxs/p/6724276.html
Copyright © 2020-2023  润新知