java集合多线程异常

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<>();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值