精华内容
下载资源
问答
  • 1、vector在所有的方法上都加上了synchronized关键字,这样保证了访问vector对象的任何方法时都必须获取对象锁,所以vector保证了不会被多线程同时访问,但是如果我的某个方法这样写:   //vector 是全局变量 ...

    1、vector在所有的方法上都加上了synchronized关键字,这样保证了访问vector对象的任何方法时都必须获取对象锁,所以vector保证了不会被多线程同时访问,但是如果我的某个方法这样写:

     

    //vector 是全局变量
    public  boolean putIfAbsent() {
       
                boolean absent = vector.contains(x); 
                if (absent) { 
                   vector.add(x);
                } 
    
        return absent; 
    }

     在contains方法判断为真之后,线程释放锁,这时在add方法被调用之前是有空隙的,其他线程完全有可能获取锁并改变vector的状态,也只有那个线程释放锁之后,add方法才会被调用。这样就产生了并发问题。

     

    2、以上说的只是其中一种问题,java1.5推出的java.uitl.concurrent包,就是为了解决复杂的并发问题的。

     

     

    写的比较简单,有问题请指正--

    展开全文
  • 最近看到书上讲到并发下ArrayList是不安全的可能会导致越界,多线程冲突访问的问题,建议改进的方法是使用vector 代替 ArrayList。脑袋里浮现出几个问题: 1.Arraylist是如何导致越界的问题? 2.vector是如何保证...

    最近看到书上讲到并发下ArrayList是不安全的可能会导致越界,多线程冲突访问的问题.建议改进的方法是使用vector 代替 ArrayList。于是乎脑袋里浮现出几个问题:
    1.Arraylist是如何导致越界的问题?
    2.vector是如何保证线程的安全的?
    3.使用vector线程就一定安全吗?
    4.vector和ArrayList分别适合在什么场景下使用

    1.ArrayList如何导致越界问题?

    1.先看一下错误代码

    import java.util.ArrayList;
    public class ArrayListMultiThread {
    	static ArrayList<Integer> al = new ArrayList<Integer>(10);
    	public static class AddThread implements Runnable {
    		public void run() {
    			for (int i = 0;i <1000000; i++) {
    				al.add(i);
    			}
    		}
    		
    	}
    	public static void main(String[] args) throws InterruptedException {
    		Thread t1 = new Thread(new AddThread());
    		Thread t2 = new Thread(new AddThread());
    		t1.start();
    		t2.start();
    		t1.join();
    		t2.join();
    		System.out.println(al.size());
    	}
    
    }
    

    运行结果如下这里出现了越界在这里插入图片描述

    2.如何导致越界?

    1.先看一下需要用到的源码

    首先,ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。
    从上面代码的报错中,我们可以看到在add方法那里出了问题,查看ArrayList中的add源码方法如下:

        /**
         * 将指定的元素追加到此列表的末尾。 
         */
        public boolean add(E e) {
       //添加元素之前,先调用ensureCapacityInternal方法
            ensureCapacityInternal(size + 1); 
            //这里看到ArrayList添加元素的实质就相当于为数组赋值
            elementData[size++] = e;//将对象e放在数组的末尾
            return true;
        }
    
    

    可以看出add方法首先调用了ensureCapacityInternal的方法,这个方法是用来得到最小的扩容量。我们再来看看ensureCapacityInternal方法:
    DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是在jdk1.8中新加的。作用是根据第一个元素被添加时知道多少个元素去填充把它与EMPTY_ELEMENTDATA区分开来.

        private void ensureCapacityInternal(int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                  // 获取默认的容量和传入参数的较大值
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
                //源码中写道  private static final int DEFAULT_CAPACITY = 10;
            }
    
            ensureExplicitCapacity(minCapacity);
        }
    

    ensureCapacituInternal方法的最后调用了ensureExplicitCapacity方法,这个方法是用来判断是否需要扩容。我们在来看一下这个方法:

      //判断是否需要扩容
        private void ensureExplicitCapacity(int minCapacity) {
            modCount++; //表现出list结构上被修改的次数
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                //调用grow方法进行扩容,调用此方法代表已经开始扩容了
                grow(minCapacity);
        }
    

    需要的源码差不多都在上面了grow方法就暂时不做描述了。

    2.ArrayList越界的逻辑

    1.假设列表大小为9,即size=9
    2.线程A开始进入add方法,这时它获取到size的值为9,调用ensureCapacityInternal方法进行容量判断。
    3.线程B此时也进入add方法,它获取到size的值也为9,也开始调用ensureCapacityInternal方法。
    4.线程A发现需求大小为10,而elementData的大小就为10,可以容纳。于是它不再扩容,返回。
    5.线程B也发现需求大小为10,也可以容纳,返回。
    6.线程A开始进行设置值操作, elementData[size++] = e 操作。此时size变为10。
    7.线程B也开始进行设置值操作,它尝试设置elementData[10] = e,而elementData没有进行过扩 容,它的下标最大为9。于是此时会报出一个数组越界的异常ArrayIndexOutOfBoundsException

    3.ArrayList导致多线程冲突访问。

    执行上面的代码我们会发现有的时候不会报错,但是结果会远小于正确值。这是由于多线程访问冲突,使的保存容器大小的变量被多线程不正常的访问,同时两个线程也同时对ArrayList中的同一个位置进行复制导致的。

    2. vector是如何保证线程安全的呢?

    上述的错误代码如果将ArrayList都换成vector,执行的结果就是正确的了。那么vector是如何保证线程的安全的呢?看源码发现vector中一些关键的方法都用synchronized修饰,这就使得每次只能有一个线程去访问,从而保证安全。

    3. 使用vector一定能保证线程的安全吗?

    很多人都会问这个问题,答案是不安全的,虽然很多方法都用synchronized修饰,保证所有对外的接口都以vector为锁,但是在单个方法的原子性方面,并不能保证符合操作的原子性。对于复合操作vector和ArrayList一样都需要进行同步处理。所以,如果是这样的话,那么用vector和ArrayList就没有区别了。因为 synchronized 的开销是巨大的,vector还会导致一些性能下降的问题。

    书上说建议使用vector代替ArrayList,看完别人的讲解发现vector还是不能乱用的,现在挺迷糊的。
    

    4.vector和ArrayList分别适合在什么场景下使用

    这个问题暂时未找到答案。o(╥﹏╥)o

    展开全文
  • 同步容器 java.util 中,存在一些古老的同步容器类,如 Vector、Hashtable。...如果单独使用同步容器所提供的操作,可以放心使用,不会带来任何的并发问题。因为同步逻辑已经被封装在该操作对应的方法中。但如果使用复

    同步容器

    java.util 中,存在一些古老的同步容器类,如 Vector、Hashtable。这些同步容器类,主要依靠持有内部锁(synchronized 修饰方法)来保证对容器状态访问的原子性。因此,几乎所有需要访问容器状态的方法,都是 synchronized 修饰的同步方法。虽然保证了线程安全,但也极大降低了并发性,使得同步容器在并发场景下堪忧的性能令人诟病。

    并发问题

    如果单独使用同步容器所提供的操作,可以放心使用,不会带来任何的并发问题。因为同步逻辑已经被封装在该操作对应的方法中。但如果使用复合操作,仍然很有可能带来并发问题,复合操作的同步逻辑需要你自己去实现。常见的复合操作有:迭代(容器遍历)、跳转(根据指定顺序找到当前元素的下一个元素)和 检查执行(先检查某一条件,该条件满足了再执行指定操作。如:若没有则添加)。Java 中,对同步容器的复合操作可能会产生异常,最常见的异常有:ConcurrentModificationException 和 ArrayIndexOutOfBoundsException。

    ConcurrentModificationException

    该异常产生的一种原因是使用迭代器时,迭代器发现容器结构在迭代过程中被修改了。虽然异常的名字是并发修改异常,但其实对于同步容器类,只要迭代器被创建开始,任何情况下容器结构被除了该迭代器的 remove 方法外的其它手段修改,再使用该迭代器的 next 方法时均会产生这个异常(并发容器不存在这个问题)。这种行为被称为 “及时失败”(fail-fast),事实上,对于非并发容器是不允许一个线程在遍历容器时, 另一个线程对它进行修改。

    那么,迭代器是如何检查迭代过程中容器是否被修改的呢?迭代器使用的是 “双计数器匹配”,即迭代器和同步容器均维护一个跟踪改写次数的计数器,迭代器在进行迭代时检查自己的计数器的值是否和容器的相同,若不同则抛出异常 。

    此外,隐式迭代器也可能造成这一异常。所谓隐式迭代器,便是某个方法内部需要使用迭代器来实现,这类方法常见的有:hashCode()、equals()、toString()、containsAll()、removeAll()、retainAll()。

    ArrayIndexOutOfBoundsException

    Vector 底层使用数组存储元素,复合操作还有可能造成数组越界。例如,下面这段代码用来遍历 Vector,使用 size() 方法确定数组边界。

    for (int i = 0; i < vector.size(); i++) {
    	System.out.println(vector.get(i));
    }
    

    在多线程环境中,如果 i = vector.size() - 1 时,经过了 i < vector.size() 条件检查后,另一个线程并发删除了 vector 中的一个元素,那么执行 vecotor.get(i) 时便会造成数组越界。

    如何避免同步容器的并发问题

    同步容器的种种问题本质还是复合操作带来的,若使用同步机制将所有的复合操作变为原子操作,如在访问容器时持有该容器的锁,必然能解决并发问题,但也牺牲了容器的性能。还有一种策略:克隆容器。譬如,我只是想遍历当前容器中的所有元素,那么完全可以使用克隆容器的迭代器做这件事,而遍历过程中,对原始容器进行修改也毫无关系。但克隆的过程存在显著性能开销,好坏取决于多个因素,包括容器大小、每个元素上执行的操作、迭代操作的频率以及响应时间、吞吐量需求。最好的方式,还是使用并发容器来代替这些古老的同步容器,并发容器拥有更细粒度的同步机制,在并发性和同步性、线程安全和容器性能间有一个较好的权衡。

    展开全文
  • 集合并发不安全问题

    2021-03-16 20:46:40
    文章目录解决集合并发不安全问题1. List并发不安全2. Set并发不安全3. Map并发不安全 解决集合并发不安全问题 1. List并发不安全 1、在并发环境下往list集合中添加或修改数据会出现ConcurrentModificationException...

    解决集合并发不安全问题

    1. List并发不安全

    1、在并发环境下往list集合中添加或修改数据会出现ConcurrentModificationException异常(并发修改异常)

    1. 方法一:使用vector集合

      List<String> list = new Vector<>();
      
    2. 方法二:使用collections工具类

      List<E> list = Collections.synchronizedList(new ArrayList<>());
      
    3. 使用java.util.concurrent下的CopyOnWriteArrayList集合

      • 其底层也是一个数组实现。private transient volatile Object[] array; 由volatile修饰;
      • 它是一个线程安全的ArrayList;
      • 使用独占锁ReentrantLock来保证同时只有一个线程对array进行修改:final transient ReentrantLock lock = new ReentrantLock();
      //创建一个并发安全的list集合
      List<String>  list = new CopyOnWriteArrayList<>();
      
      public CopyOnWriteArrayList() {
          setArray(new Object[0]);
      }
      final void setArray(Object[] a) {
          array = a;  //array是不可序列化且由volatile修饰的
      }
      private transient volatile Object[] array;  //底层源码
      

    2、CopyOnWrite :写入时复制 。简称COW 是一种优化策略。

    • 多线程使用list,写入时可能会存在覆盖操作。先复制一份,再给调用者。
    • 读写分离

    3、如果让我们自己做一个写时复制的线程安全的list应该怎么做???需要考虑以下几点:

    1. 何时初始化list,初始化的list元素个数为多少,list是有限大小吗??
    2. 如何保证线程安全,比如多个线程进行读写时如何保证是线程安全的??
    3. 如何保证使用迭代器遍历list时的数据一致性??

    解决上述三个问题:

    //无参构造:创建一个大小为 0 的数组作为array的初始值
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }
    
    //两个有参构造初始化数组
    //1、创建一个list,其内部元素是入参toCopyIn的副本
    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }
    
    //入参为一个集合,将集合里面的元素复制到 list集合中
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
            elements = c.toArray();
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elements.getClass() != Object[].class)
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
        setArray(elements);
    }
    

    添加元素的实现:

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;  //获取独占锁,如果多个线程都调用add方法则只有一个线程会获取到该锁,其他线程会被阻塞挂起直到锁被释放。保证线程安全
        lock.lock();
        try {
            Object[] elements = getArray(); //获取array
            //复制array到一个新数组(新数组是原数组的大小加 1, 所以CopyOnWriteArrayList是一个无界数组),并把新增的元素添加到新数组。
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            //使用新数组替换添加前的数组
            setArray(newElements);
            return true;
        } finally {
            //在返回前释放锁,先执行finally代码块,再执行return语句
            lock.unlock();
        }
    }
    

    总结

    • CopyOnWriteArrayList使用写时复制的策略来保证List的一致性。
    • 获取-修改-写入三步操作不是原子性的,所以在增删改的过程中都使用了独占锁,来保证在某个时间只有一个线程能对 list的数组进行修改
    • CopyOnWriteArrayList 提供了弱一致性的迭代器,即保证在获取迭代器后,其他线程对list的修改是不可见的(即修改是获取下一个迭代器时才生效)

    2. Set并发不安全

    方法一:使用collections工具类

     Set<String> set= Collections.synchronizedSet(new HashSet<>());
    

    方法二:使用java.ytil.concurrent下的CopyOnWriteArraySet集合。

    Set<String> set= new CopyOnWriteArraySet<>();
    

    HashSet的底层就是一个HashMap。

    public HashSet() {
        map = new HashMap<>();
    }
    
    // HashSet的add()方法的底层
    public boolean add(E e) {
            return map.put(e, PRESENT)==null;
        }
    private static final Object PRESENT = new Object(); //不变的值
    

    3. Map并发不安全

    Map<String, String> map = new HashMap<>();
    
    1. map是这样用的吗? 不是。

    2. 默认等价于什么? Map<String, String> map = new HashMap<>(16, 0.75);

    3. 加载因子(0.75),初始化容量(16)

      • 底层是写成位运算:1 < < 4 (初始化容量);
      • 加载因子:static final float DEFAULT_LOAD_FACTOR = 0.75f;
      • 最大容量: 1 << 30;
    4. 解决:使用JUC包下的ConcurrentHashMap集合

      import java.util.concurrent.ConcurrentHashMap;
      
      Map<String, String> map =new ConcurrentHashMap<>();
      
    展开全文
  • 一个经典问题Vector到底是不是线程安全的?很多人都会回答,是,vector是线程安全的。诚然,不止是很多IT教材上是这么写的,就连JDK的作者写的注释也是这么说的。如下图(图为 JDK中Vector的源代码):注释中,红框...
  • /** * 解决ArrayList的三种方法 */ //1. 线程安全 add 方法使用... list = new Vector<>(); //2. Collections 工具类转换成线程安全 //List<String> list = Collections.synchronizedList(new Ar
  • 上一篇构建的数据库连接池,今天拿来在项目中测试时,发现在处理高并发问题上有很明显的缺点。现在回过头来看我们上一篇的代码:public class ConnectionPool {/*线程安全数组*/private volatile Vector pool;...
  • 问题一:ArrayList为什么会出现并发问题? ArrayList是线程不安全的,在多线程并发访问的时候可能会出现问题,如果想使用线程安全的集合类,java自带有vector,也就是说vector是线程安全的。但是arayList的底层是...
  • 1 .从最基础的地方做起,优化我们写的代码,减少必要的...使用java中效率高的类,比如ArrayList比Vector性能好。 html静态化 我们通过一个链接地址访问,通过这个链接地址,服务器对应的模块处理这个请求,转到对应
  • 一个经典问题Vector到底是不是线程安全的? 很多人都会回答,是,vector是线程安全的。诚然,不止是很多IT教材上是这么写的,就连JDK的作者写的注释也是这么说的。如下图(图为 JDK中Vector的源代码):注释中,...
  • 解决办法 public class Test08 { ... // 并发下ArrayList安全吗,Synchronized; /** * 解决方法: * 1、List<String> list = new Vector<>(); * 2、List<String> list = Collections.
  • 使用Vector()数组来解决这个问题2. 使用Connections集合3. 使用 CopyOnWriteArrayList ArrayList在多并发下的不安全问题以及解决方法 为啥不安全 多线程并发插入元素和读取元素的时候会出现 ...
  • 一:概述C++11引入了thread类,大大降低了多线程使用的复杂度,原先使用多线程只能用系统的API,无法解决跨平台问题,一套代码平台移植,对应多线程代码也必须要修改。现在在C++11中只需使用语言层面的thread可以...
  • (3)元素的数据类型问题:数组可以存储基本数据类型,集合只能存储引用类型 集合类的特点:集合只用于存储对象,集合长度是可变的,集合可以存储不同类型的对象 Collection:是集合的顶层接口,
  • 不过,vector的大小可以根据需要增大或缩小,以适应创建vector后进行添加或移除项的操作,因此不需要考虑元素是否越界或者会不会浪费内存的问题。由vector的iterator和listIterator方法所返回的迭代器是快速失败的:...
  • 1,new Vector():这个回答不太好,也不实用,vector的jdk是1.0出来的,ArrayList版本是1.2才出来 2,Collections.synchronizedList(new ArrayList()) ; 用顶级父类去访问, 3,new CopyOnWriteArrayList<>(); 可以...
  • Java并发编程04:并发容器并发容器的引出: 售票问题实现1:使用List-非原子性操作实现2:使用Vector-判断与操作分离,复合操作不保证原子性实现3: 使用同步代码块锁住复合操作-保证正确性但效率低实现4: 使用并发队列,先...
  • Collections.synchronizedXxx()同步容器等相比,concurrent中引入的并发容器主要解决了两个问题: 1)根据具体场景进行设计,尽量避免synchronized,提供并发性。 2)定义了一些并发安全的复合操作,并且保证并发...
  • 我们先通过一个简单的代码来了解该问题。 同步问题 我们使用一个简单的结构体 Counter,该结构体包含一个值以及一... std::vector<std> threads; for(int i = 0; i < 5; ++i){ threads.push_back(std::thread([&c
  • Java高效并发中的“高效”问题:   1. Java中的线程安全: (“如果多个线程访问一个对象时,...,调用结果都是正确的,则是线程安全”)  (1) 不可变:(final关键字)  (2) 绝对线程安全:(较少,很难...
  • Java集合框架中的Vector集合通过对可能引发线程安全问题的方法(例如:add()、remove()、size()等等)加synchronized关键字实现了线程安全,但这种实现仅能在一定程度上减少多线程并发操作下出现问题的可能性,例如...
  • 文章目录并发容器的引出: 售票问题实现1:使用List-非原子性操作实现2:使用Vector-判断与操作分离,复合操作不保证原子性实现3: 使用同步代码块锁住复合操作-保证正确性但效率低实现4: 使用并发队列,先取票再判断并发...
  • * ConcurrentModificationException 并发修改异常 * 解决方案: * 1、List<String> list = new Vector<>();//jdk 1.0 就存在的!效率低 * 2、List<String> list = Collections....
  • import java.util.ArrayList;...import java.util.Vector; /** * 并发下的ArrayList */ public class ArrayListMultiThread { static ArrayList<Integer> arrayList = new ArrayList<>(); // s...
  • 1.集合类不安全之并发修改异常之list: 1)如果直接new一个ArrayList的话,它的长度默认为10. 2)如果超过10,就会扩容原空间的一半。...​ 使用Vector()但是并发能力有问题。 ​ 使用Collections.sunchronizedLi
  • Java中vector的用法

    万次阅读 2014-06-08 20:58:37
    不过,vector的大小可以根据需要增大或缩小,以适应创建vector后进行添加或移除项的操作,因此不需要考虑元素是否越界或者会不会浪费内存的问题。 由vector的iterator和listIterator方法所返回的迭代器是快速失败的...
  • 并发编程9-并发容器

    2019-08-01 18:27:37
    解决并发情况下的容器线程安全问题. 譬如:Vector,HashTable,都是给予同步锁实现的. concurrent包下的类,大部分都是使用系统底层的实现,类似于native; Map/set ConcurrentHashMap/ConcurrentSkipListMap 这两...
  • 问题为什么Java Vector被认为是遗留类,已过时或已弃用?在使用并发时,它的使用是否有效?如果我不想手动同步对象而只想使用线程安全的集合而不需要制作底层数组的新副本(如CopyOnWriteArrayList那样),那么使用...
  • 解决并发情况下的容器线程安全问题的。给多线程环境准备一个线程安全的容器对象。 线程安全的容器对象: Vector, Hashtable。线程安全容器对象,都是使用 synchronized 方法实现的。 concurrent 包中的同步容器,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 410
精华内容 164
关键字:

vector并发问题