List
ArrayList 为例,引用的使用的是 java17,所以源码不同版本的 jdk 可能有所不同。
public class ThreadDemo4 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i <30; i++) {
new Thread(()->{
// 向集合添加内容
list.add(UUID.randomUUID().toString().substring(0,8));
// 从集合获取内容,下面一行会发生异常,是取元素的时候,因此是 add 方法线程不安全
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
Exception in thread "9" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:456)
at java.base/java.lang.String.valueOf(String.java:4222)
at java.base/java.io.PrintStream.println(PrintStream.java:1047)
at com.collect.ThreadDemo4.lambda$main$0(ThreadDemo4.java:34)
at java.base/java.lang.Thread.run(Thread.java:842)
java.util.ConcurrentModificationException,因此,ArrayList 是非线程安全的。
解决方案(一):ArrayList 改为 java.util.concurrent 包下的 CopyOnWriteArrayList
List<String> list = new ArrayList<>();
// 改为下面的
List<String> list = new CopyOnWriteArrayList<>();
整体上来说,CopyOnWriteArrayList 就是利用锁 + 数组拷贝 + volatile 关键字保证了 List 的线程安全。写时复制技术。看下面的源码,注意,这里的锁用的是 synchronized ,之前是 ReentrantLock,应该是后期的优化。
public boolean add(E e) {
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
es = Arrays.copyOf(es, len + 1);
es[len] = e;
setArray(es);
return true;
}
}
CopyOnWriteArrayList 读操作(不加锁)性能很高,因为无需任何同步措施,比较适用于读多写少的并发场景。Java的list在遍历时,若中途有别的线程对list容器进行修改,则会抛ConcurrentModificationException异常。而CopyOnWriteArrayList由于其"读写分离"的思想,遍历和修改操作分别作用在不同的list容器,所以在使用迭代器进行遍历时候,也就不会抛出ConcurrentModificationException异常了。
解决方案(二):
使用 Collections 工具类提供的 public static <T> List<T> synchronizedList(List<T> list)
方法
List<String> list = new ArrayList<>();
// 改为下面的
List<String> list = new ArrayList<>();
List<String> list = Collections.synchronizedList(new ArrayList<>());
由源码可知,其实现线程安全的方式是建立了 list 的包装类
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
SynchronizedList 对部分操作加上了 synchronized 关键字以保证线程安全,效率低。
public int hashCode() {
synchronized (mutex) {return list.hashCode();}
}
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
public int indexOf(Object o) {
synchronized (mutex) {return list.indexOf(o);}
}
public int lastIndexOf(Object o) {
synchronized (mutex) {return list.lastIndexOf(o);}
}
并且其 iterator() 方法没有加锁处理。在不加锁的情况下使用遍历,如果此时有线程对 list 进行修改和删除操作就会产生脏数据,所以如果我们对数据的要求较高,想要避免这方面问题的话,在遍历的时候也需要加锁进行处理。
public Iterator<E> iterator() {
return c.iterator(); // Must be manually synched by user!
}
解决方案(三):
ArrayList 改为 Vector
List<String> list = new ArrayList<>();
// 改为下面的
List<String> list = new Vector<>();
为什么java不推荐使用 Vector?
1、 Vector 在每个可能出现线程安全的方法上都加了 synchronized 关键字,效率低;
2、Vector 空间满了之后,扩容是一倍,而ArrayList仅仅是一半;
3、Vector 分配内存的时候需要连续的存储空间,如果数据太多,容易分配内存失败;
Set
HashSet 为例,引用的使用的是 java17,所以源码不同版本的 jdk 可能有所不同。
public class ThreadDemo4 {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 0; i <30; i++) {
new Thread(()->{
// 向集合添加内容
set.add(UUID.randomUUID().toString().substring(0,8));
// 从集合获取内容,下面一行会发生异常,是取元素的时候,因此是 add 方法线程不安全
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
Exception in thread "14" java.util.ConcurrentModificationException
at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1597)
at java.base/java.util.HashMap$KeyIterator.next(HashMap.java:1620)
at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:456)
at java.base/java.lang.String.valueOf(String.java:4222)
at java.base/java.io.PrintStream.println(PrintStream.java:1047)
at com.collect.ThreadDemo4.lambda$main$0(ThreadDemo4.java:47)
at java.base/java.lang.Thread.run(Thread.java:842)
java.util.ConcurrentModificationException,因此,HashSet 是非线程安全的。
解决方案(一):HashSet 改为 java.util.concurrent 包下的 CopyOnWriteArraySet
Set<String> set = new HashSet<>();
// 改为下面的
Set<String> set = new CopyOnWriteArraySet<>();
HashSet 的底层其实就是 HashMap
public HashSet() {
map = new HashMap<>();
}
Set 中元素的特点就是无序、不可重复,HashSet 的元素就是 HashMap 的 key
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
Map
HashMap 为例,引用的使用的是 java17,所以源码不同版本的 jdk 可能有所不同。
public class ThreadDemo4 {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
for (int i = 0; i <30; i++) {
String key = String.valueOf(i);
new Thread(()->{
// 向集合添加内容
map.put(key,UUID.randomUUID().toString().substring(0,8));
// 从集合获取内容
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
Exception in thread "13" java.util.ConcurrentModificationException
at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1597)
at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1630)
at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1628)
at java.base/java.util.AbstractMap.toString(AbstractMap.java:550)
at java.base/java.lang.String.valueOf(String.java:4222)
at java.base/java.io.PrintStream.println(PrintStream.java:1047)
at com.collect.ThreadDemo4.lambda$main$0(ThreadDemo4.java:27)
at java.base/java.lang.Thread.run(Thread.java:842)
java.util.ConcurrentModificationException,因此,HashSet 是非线程安全的。
解决方案(一):HashMap 改为 java.util.concurrent 包下的 ConcurrentHashMap
Map<String,String> map = new HashMap<>();
// 改为下面的
ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();