多线程5-同步容器和并发容器(代码片段)

zhangblearn zhangblearn     2023-01-12     766

关键词:

同步容器出现的原因?

  在Java的集合容器框架中,主要四大类是List、Set、Queue、Map。其中List、Set、Queue分别继承了Collection顶层接口,Map本身是一个顶层接口。我们常用的ArrayList、LinkedList、HashMap这些容器都是非线程安全的,如果有多个线程并发访问这些容器时,就会出现问题。因此,编写程序时,必须要求开发者手动在任何访问到这些容器的地方进行同步处理,这样导致使用这些容器时的不便,所以,Java提供了同步容器供用户使用。


 Java中的同步容器类:

  1、Vector、Stack、HashTable

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

  Vector实现了List接口,其实际上就是一个类似于ArrayList的数组,但Vector中的方法都是synchronized方法。Stack也是同步容器,实际上Stack是继承于Vector。HashTable也进行了同步处理,但HashMap没有。Collections类是一个工具提供类,其中包含对集合或容器进行排序、查找等操作的方法。重要的是,它也提供了几个静态工厂方法来创建同步容器类。

技术分享图片


 同步容器存在的缺陷:

  1、同步容器中的方法采用了synchronized进行同步,这会影响执行性能。

  2、同步容器不一定是真正完全的线程安全,同步容器中的方法是线程安全的,但对这些集合类的符合操作无法保证其线程安全性,仍旧需要通过主动加锁来保证。

  3、ConcurrentModificationException异常

    Vector等容器迭代时同时对其修改,会报ConcurrentModificationException异常。


 ConcurrentModificationException异常详解:

public class Test 
    public static void main(String[] args)  
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(2);
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext())
            Integer integer = iterator.next();
            if(integer==2)
                list.remove(integer);
        
    

//结果

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.testdemo.demo.Test.main(Test.java:20)

 执行上述代码后,会报该异常。并发现错误发生在checkForComodification()方法中,我们不直接看该方法内容。先了解一下ArrayList中itreator方法的具体实现。该方法存在于父类AbstractList中。

public Iterator<E> iterator() 
return new Itr();

该方法返回一个指向Itr类型对象的引用。看看其具体实现。

private class Itr implements Iterator<E> 
//表示下一个要访问的元素的索引
int cursor = 0;
  //表示上一个要访问的元素的索引
int lastRet = -1;
  //expectedModCount表示对ArrayList修改次数的期望值,初始值为modCount
  //modCount是AbstractList类中的一个成员变量。表示对List的修改次数。每次调用add或remove方法都会对modCount进行+1操作
int expectedModCount = modCount;

  //判断是否还有元素未被访问
public boolean hasNext() return cursor != size();
  //获取到下标为0的元素
public E next() checkForComodification(); try E next = get(cursor); lastRet = cursor++; return next; catch (IndexOutOfBoundsException e) checkForComodification(); throw new NoSuchElementException(); public void remove() if (lastRet == -1) throw new IllegalStateException(); checkForComodification(); try AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; expectedModCount = modCount; catch (IndexOutOfBoundsException e) throw new ConcurrentModificationException();   //如果 final void checkForComodification() if (modCount != expectedModCount) throw new ConcurrentModificationException();

  首先调用list.iterator()返回一个Iterator后,通过hashNext方法判刑是否还有元素未被访问。该方法通过对比下一个访问的元素下标和ArrayList的大小。不等于时说明还有元素需要访问。

然后通过next方法获取到下标为0的元素。该方法中先调用checkForComodification()方法,根据cursor的值获取到元素。将cursor值赋给lastRet。初始时,cursor为0,lastRet为-1.调用一次后curosr的值为1,lastRet为0.此时modCount为0,expectedModCount也为0。

  当元素值为2时,调用list.remove()方法。我们看一下ArrayList中remove方法的源码。

 public boolean remove(Object o) 
        if (o == null) 
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) 
                    fastRemove(index);
                    return true;
                
         else 
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) 
                    fastRemove(index);
                    return true;
                
        
        return false;
    

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) 
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    

   remove方法删除元素实际是调用fastRemove()方法,在该方法中,首先对modCount进行加1,表示对集合修改了一次。然后删除元素,最终将size进行减1,并将引用设置为null方便垃圾回收。此时对于iterator,expectedModCount为0,cursor为1,lastRet为0。对于list,其modCount为1,size为0。当执行完删除操作,继续while循环。调用hasNext()方法。此时的cursor为1,size为0,返回true继续执行循环,调用next()方法。next方法首先调用checkForComodification()对比modCount和expectedModCount。此时值不一样,抛出ConcurrentModificationException异常。关键之处在于:list.remove会导致modCount和expectedModCount不一致。


ConcurrentModificationException异常解决:

  单线程:单线程情况下,采用迭代器提供的remove方法进行删除就不会抛出异常。

  多线程:在使用iterator迭代的时候,使用synchronized或lock同步、使用并发容器CopyOnWriteArrayList代替ArrayList和Vector


 并发容器

  同步容器将所有对容器状态的访问都串行化了,保证线程安全性的同时严重降低了并发性,当多个线程竞争容器时,吞吐量严重降低。从JDK5开始针对多线程并发访问设计,提供了并发性能较好的并发容器,引入了java.util.concurrent 包。

  与Vector和Hashtable、Collection.synchronizedXxx()同步容器等相比,util.concurrent中的并发容器主要解决了两个问题:

  1、根据具体场景设计,尽量避免synchronized,提供并发性

  2、定义一些并发安全的复合操作,并且保证并发环境下的迭代操作不会出错。util.concurrent中容器在迭代时,可以不封装在synchronized中,可以保证不抛异常,但未必每次看到的都是最新的数据。

  并发容器简单介绍:

  ConcurrentHashMap代替同步的Map。HashMap是根据散列值分段存储,同步Map在同步时锁住了所有的段,而ConcurrentHashMap加锁的时候根据散列值锁住了散列值对应的那段,提高了并发性能。ConcurrentHashMap提供了对常用符合操作的支持。比如"若没有则添加":putIfAbsent(),替换:replace()。这2个操作都是原子操作。

HashMap和ConcurrentHashMap的区别:
    1、HashMap不是线程安全的,而ConcurrentHashMap是线程安全的。
    2、ConcurrentHashMap采用锁分段技术,将整个Hash桶进行了分段segment,也就是将这个大的数组分成了几个小的片段segment,而且每个小的片段segment上面都有锁存在,那么在插入元素的时候就需要先找到应该插入到哪一个片段segment,然后再在这个片段上面进行插入,而且这里还需要获取segment锁。
    3、ConcurrentHashMap可以做到读取数据不加锁,并且其内部的结构可以让其在进行写操作的时候能够将锁的粒度保持地尽量地小,不用对整个ConcurrentHashMap加锁,并发性能更好。

  CopyOnWriteArrayList和CopyOnWriteArraySet分别代替List和Set。主要是在遍历操作为主的情况下代替同步的List和同步的Set。

  ConcurrentLinedQueue是一个先进先出的非阻塞队列。

  ConcurrentSkipListMap可以在高效并发中替代SoredMap、ConcurrentSkipListSet可以在高效并发中替代SoredSet。

 

  

 
















java并发编程之工具类(代码片段)

java在线程同步和互斥方面在语言和工具方面都提供了相应的支撑,与此同时,java还提供了一系列的并发容器和原子类,来使得并发编程更容易。一。并发容器(一)。同步容器同步容器指的是容器本身使用synchronized关键字来同... 查看详情

多线程之同步容器

Java并发编程:同步容器  为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器、并发容器、阻塞队列、Synchronizer(比如CountDownLatch)。今天我们就来讨论下同步容器。  以下是本文... 查看详情

并发编程-线程安全策略之两种类型的同步容器(代码片段)

脑图概述之前讲了一些常用的线程不安全的集合容器(ArrayList、HashMap、HashSet),如果有多个线程并发访问这些集合时就会出现线程不安全的问题。当我们在使用这些容器时,需要我们自己来处理线程安全的问题... 查看详情

java并发编程学习6-同步容器类和并发容器(代码片段)

...chronizedXxx等工厂方法创建的同步的封装器类。这些类实现线程安全性的方法是:将它们的状态封装起来,并对每个公有方法都进行同 查看详情

多线程编程-之并发编程:同步容器

为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器、并发容器、阻塞队列、Synchronizer。同时说说List,Set,Map之间的区别. 自动扩展的数组:List 重复的数组:set 自动排序的组数:TreeSe... 查看详情

并发编程-并发容器(j.u.c)核心abstractqueuedsynchronizer抽象队列同步器aqs介绍(代码片段)

J.U.C脑图  了体现出AQS和线程池的重要性,上图单独将AQS和线程池拿出来了。J.U.C的构成如下:J.U.C核心AQS简介  AQS(AbstractQueuedSynchronizer)是并发容器中的同步器,AQS是J.U.C的核心,它是抽象的队列式的同... 查看详情

并发容器和框架之concurrenthashmap(代码片段)

了解HashMap的人都知道HashMap是线程不安全的(多线程下的put方法达到一定大小,引发rehash,导致闭链,最终占满CPU),同时线程安全的HashTable效率又令人望而却步(每个方法都进行同步,效率低下),所以在这种情境下为并发而... 查看详情

并发编程-线程安全策略之并发容器(j.u.c)中的集合类(代码片段)

...同步的,所以性能较差。而且同步容器也并不是绝对线程安全的,在一些特殊情况下也会出现线程不安全的行为。那么有没有更好的方式代替同步容器呢?---->那就是并发容器,有了并发容器后同步容器的使用... 查看详情

day839.并发容器-java并发编程实战(代码片段)

...关于并发容器。Java1.5之前提供的同步容器虽然也能保证线程安全,但是性能很差。Java1.5版本之后提供的并发容器在性能方面则做了很多优化,并且容器的类型也更加丰富了。一、同步容器及其注意事项Java中的容器主要... 查看详情

day839.并发容器-java并发编程实战(代码片段)

...关于并发容器。Java1.5之前提供的同步容器虽然也能保证线程安全,但是性能很差。Java1.5版本之后提供的并发容器在性能方面则做了很多优化,并且容器的类型也更加丰富了。一、同步容器及其注意事项Java中的容器主要... 查看详情

同步类容器和并发类容器

 一同步类容器同步类容器都是线程安全的,但在某些场景中可能需要加锁来保证复合操作。符合操作如:迭代(反复访问元素,遍历完容器中所有元素)、跳转(根据指定的顺序找到当前元素的下一个元素)、条件运算。这... 查看详情

java并发编程学习6-同步工具类和并发容器(代码片段)

...chronizedXxx等工厂方法创建的同步的封装器类。这些类实现线程安全性的方法是:将它们的状态封装起来,并对每个公有方法都进行同 查看详情

多线程之并发容器concurrenthashmap(代码片段)

简介ConcurrentHashMap是util.concurrent包的重要成员。本文将结合Java内存模型,分析JDK源代码,探索ConcurrentHashMap高并发的具体实现机制。由于ConcurrentHashMap的源代码实现依赖于Java内存模型,所以阅读本文需要读者了解Java内存模型。同... 查看详情

并发队列(代码片段)

... 1.同步容器      Vector容器,HashTable容器,都是线程安全      如果同步容器使用foreach迭代过程中修改了元素的值,则会出现ConcurrentModificationException异常      可以使用iterator迭代器解决,但是在多线程并... 查看详情

从零开始学多线程之构建快(代码片段)

前文回顾上一篇博客从零开始学多线程之组合对象(三)主要讲解了:1.设计线程安全的类要考虑的因素.2. 对于非线程安全的对象,我们可以考虑使用锁+实例限制(Java监视器模式)的方式,安全的访问它们.3.扩展线程安全类的四种方... 查看详情

架构师养成--7.同步类容器和并发类容器

一、同步类容器同步类容器都是线程安全的,但在某些场景下可能需要加锁来保护复合操作。复合类操作如:迭代(反复访问元素,遍历完容器中的所有元素)、跳转(根据指定的顺序找到当前元素的下一个元素)、以及条件运算。... 查看详情

java线程同步类容器和并发容器

同步类容器都是线程安全的,在某些场景下,需要枷锁保护符合操作,最经典ConcurrentModifiicationException,原因是当容器迭代的过程中,被并发的修改了内容。for(Iteratoriterator=tickets.iterator();iterator.hasNext();) Stringstring=(String)iterator.n... 查看详情

多线程与高并发基础三

多线程顺序打印A1B2C3到Z26面试题:写一个固定容量同步容器,拥有put和get方法,能够支持2个生产者线程以及10个消费者线程的阻塞调用实现一个容器,提供两个方法,add,size写两个线程,线程1添加10个元素到容器中,线程2实现... 查看详情