JUC之集合中的线程安全问题
集合线程安全问题
JDK Version:9
首先说下集合线程安全是什么:当多个线程对同一个集合进行添加和查询的时候,出现异常错误。
复现例子:
package com.JUC; import java.util.ArrayList; import java.util.List; import java.util.UUID; public class ListSecutity04 { public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0; i < 100; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list); },String.valueOf(i)).start(); } } }
效果图:
可以看到报
ConcurrentModificationException并发修改异常;出现该错误的问题是,在ArrayList中的add方法没有加锁。
查看其源码:
public boolean add(E e) { modCount++; add(e, elementData, size); return true; } //---------------------------------------- public void add(int index, E element) { rangeCheckForAdd(index); modCount++; final int s; Object[] elementData; if ((s = size) == (elementData = this.elementData).length) elementData = grow(); System.arraycopy(elementData, index, elementData, index + 1, s - index); elementData[index] = element; size = s + 1; } //---------------------------------------- private void add(E e, Object[] elementData, int s) { if (s == elementData.length) elementData = grow(); elementData[s] = e; size = s + 1; }
解决方式-:Vector和Conllections
见名知意,标题的两种方法都是比较古老的方法,使用Vector是因为其add方法中加的sychronized关键字修饰的
public synchronized boolean add(E e) { modCount++; add(e, elementData, elementCount); return true; }
另一种方法是在Collection中的工具类
synchronizedCollection(Collection<T> c)返回指定 collection 支持的同步(线程安全的)collection。
反复执行测试,代码通过:
package com.JUC; import java.util.*; public class ListSecutity04 { public static void main(String[] args) { // List<String> list = new ArrayList<>(); // List<String> list = new Vector<>(); List<String> list = Collections.synchronizedList(new ArrayList()); for (int i = 0; i < 100; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list); },String.valueOf(i)).start(); } } }
虽然但是,我们选择
CopyOnWriteArrayList;
解决方式二:CopyOnWriteArrayList
写时复制技术:
List<String> list = new CopyOnWriteArrayList<>();
其思想:
在多线程的情况下,当对集合进行写的操作的时候,系统先将原来的内容(A)复制一份为(B),原来的内容(A)可以进行并发读,复制后的内容(B)写入新的内容,当内容写入完成后,A与B实现覆盖或者说合并。
其中数组的定义我们使用的是volatile定义的,这样,每个线程可以实时的观察到数组的变化。
private transient volatile Object[] array; final void setArray(Object[] a) { array = a; }
add()方法源码:对lock对象加了锁
final transient Object lock = new Object();
public boolean add(E e) { synchronized (lock) { Object[] elements = getArray(); //获取原内容 int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); //数组复制 newElements[len] = e; //添加新的内容 setArray(newElements); //覆盖原数组 return true; } }
CopyOnWriteArrayList最大特点,读写分离,最终一致。比synchronized悲观锁性能较好。缺点就是,复制需要占用内存,可能出现OOM的情况。
与之类似线程不安全的集合有:HashMap,HashSet,其解决方法类似,在JUC中都有对应,
我们也可以进行代码的编写,并进入源码简单分析一下:
HashSet--CopyOnWriteArraySet
首先查看HashSet中的add方法的源码:(线程不安全)
public boolean add(E e) { return map.put(e, PRESENT)==null; } //---------map中 public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
从中可以看到,set集合底层使用的是map集合中的put方法,进入其方法中查看,是没有同步相关的代码修饰的。
也可以看出来Set集合是无序且不重复的,set中传入的E最后被传入到map中作为key。
然后查看下CopyOnWriteArraySet中add的源码:(线程安全)
public boolean add(E e) { return al.addIfAbsent(e); //CopyOnWriteArrayList<E> al = new CopyOnWriteArrayList<E>() } //-------------addIfAbsent--------------- public boolean addIfAbsent(E e) { Object[] snapshot = getArray(); return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false : addIfAbsent(e, snapshot); } //---------------------addIfAbsent------------------------ private boolean addIfAbsent(E e, Object[] snapshot) { synchronized (lock) { Object[] current = getArray(); int len = current.length; if (snapshot != current) { // Optimize for lost race to another addXXX operation int common = Math.min(snapshot.length, len); for (int i = 0; i < common; i++) if (current[i] != snapshot[i] && Objects.equals(e, current[i])) return false; if (indexOf(e, current, common, len) >= 0) return false; } Object[] newElements = Arrays.copyOf(current, len + 1); newElements[len] = e; setArray(newElements); return true; }
从源码可以看到,set使用的是CopyOnWriteArrayList,最终添加的数据在addIfAbsent方法中进行了同步。
HashMap--ConcurrentHashMap
HashMap解决的方式就是ConcurrentHashMap,其源码中采用的也是synchronized修饰的,具体的添加的流程,代码没读懂,暂且不表。 欢迎朋友分享下该部分的优质博客。
- 集合遍历的线程安全问题
- 关于Java集合类中线程安全与不安全问题笔录
- 线程(集合的线程安全问题)
- 集合的线程安全问题
- 集合框架的线程安全问题
- 集合中的线程安全问题
- Java中集合的线程不安全问题
- Java集合的有序无序问题和线程安全与否问题
- 集合的线程安全问题
- java集合中的线程安全问题
- Structs的线程安全问题
- 关于CoreData和SQLite多线程访问时的线程安全问题
- Servlet和JSP的线程安全问题
- 6、从volatile说到i++的线程安全问题
- 关于servlet的线程安全问题
- Java集合的线程安全用法
- 力所能及之java线程安全和非线程安全问题
- Java 单例模式线程安全问题
- java线程安全问题之静态变量、实例变量、局部变量
- 初学设计模式(3)-----单例模式(在研究单例的线程安全问题时,发现一篇很全面的文章,直接转了)