Java集合多线程安全

Posted Raicho

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java集合多线程安全相关的知识,希望对你有一定的参考价值。

线程安全与不安全集合

线程不安全集合:

  • ArrayList
  • LinkedList
  • HashMap
  • HashSet
  • TreeMap
  • TreeSet
  • StringBulider

线程安全集合:

  • Vector
  • HashTable
  • Properties

集合线程安全与解决方案

ArrayList线程安全问题

package com.raicho.mianshi.mycollection;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * @author: Raicho
 * @Description:
 * @program: mianshi
 * @create: 2020-07-17 15:32
 **/
public class ArrayListConcurrentDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 30; ++i) {
            new Thread(() -> {
                list.add(UUID.randomUUID().randomUUID().toString().substring(0, 4));
                System.out.println(list);
            }).start();
        }
    }
}

运行报错:


ArrayList是线程不安全的,add()方法并没有加锁(synchronized),多线程环境下会抛出ConcurrentModificationException

解决方案:

  • 使用Vector类(使用了synchronized),效率极低

  •  使用Collections.synchronizedList(new ArrayList<>()):内部直接将接受的List对象传递给静态内部类SynchronizedList对象,然后Collections.synchronizedList(new ArrayList<>())返回的List对象的调用方法都是直接调用输入List对象的方法,但是加了synchronized,类似装饰器模式,也是对输入List的一种增强:
package com.raicho.mianshi.mycollection;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

/**
 * @author: Raicho
 * @Description:
 * @program: mianshi
 * @create: 2020-07-17 15:32
 **/
public class ArrayListConcurrentDemo {

    public static void main(String[] args) {
        List<String> list = Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i < 50; ++i) {
            new Thread(() -> {
                list.add(UUID.randomUUID().randomUUID().toString().substring(0, 4));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

源码:

static <T> List<T> synchronizedList(List<T> list, Object mutex) {
    return (list instanceof RandomAccess ?
            new SynchronizedRandomAccessList<>(list, mutex) :
            new SynchronizedList<>(list, mutex));
}

static class SynchronizedList<E>
    extends SynchronizedCollection<E>
    implements List<E> {
    private static final long serialVersionUID = -7754090372962971524L;

    final List<E> list;

    SynchronizedList(List<E> list) {
        super(list);
        this.list = list;
    }
    SynchronizedList(List<E> list, Object mutex) {
        super(list, mutex);
        this.list = list;
    }

    public boolean equals(Object o) {
        if (this == o)
            return true;
        synchronized (mutex) {return list.equals(o);}
    }
    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);}
    }

    public boolean addAll(int index, Collection<? extends E> c) {
        synchronized (mutex) {return list.addAll(index, c);}
    }

    public ListIterator<E> listIterator() {
        return list.listIterator(); // Must be manually synched by user
    }

    public ListIterator<E> listIterator(int index) {
        return list.listIterator(index); // Must be manually synched by user
    }

    public List<E> subList(int fromIndex, int toIndex) {
        synchronized (mutex) {
            return new SynchronizedList<>(list.subList(fromIndex, toIndex),
                                        mutex);
        }
    }

    @Override
    public void replaceAll(UnaryOperator<E> operator) {
        synchronized (mutex) {list.replaceAll(operator);}
    }
    @Override
    public void sort(Comparator<? super E> c) {
        synchronized (mutex) {list.sort(c);}
    }

    private Object readResolve() {
        return (list instanceof RandomAccess
                ? new SynchronizedRandomAccessList<>(list)
                : this);
    }
}
  • CopyOnWriteArrayList:写时复制是一种读写分离的思想,在并发读的时候不需要加锁,因为它能够保证并发读的情况下不会添加任何元素。而在并发写的情况下,需要先加锁,但是并不直接对当前容器进行写操作。而是先将当前容器进行复制获取一个新的容器,进行完并发写操作之后,当之前指向原容器的引用更改指向当前新容器。也就是说,并发读和并发写是针对不同集合,因此不会产生并发异常
package com.raicho.mianshi.mycollection;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author: Raicho
 * @Description:
 * @program: mianshi
 * @create: 2020-07-17 15:32
 **/
public class ArrayListConcurrentDemo {

    public static void main(String[] args) {
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 30; ++i) {
            new Thread(() -> {
                list.add(UUID.randomUUID().randomUUID().toString().substring(0, 4));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

源码:

// CopyOnWriteArrayList.java
public boolean add(E e) {
    // 写操作加锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 原有容器复制一份
        Object[] elements = getArray();
        int len = elements.length;
        // 创建一个容器,将原来的数据复制到新容器中,并且还有一个位置空余
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 将新元素添加到空余位置
        newElements[len] = e;
        // 将原来指向旧容器的引用指向新容器
        setArray(newElements);
        return true;
    } finally {
        // 写操作完成,解锁
        lock.unlock();
    }
}

public E set(int index, E element) {
    // 更新操作类似
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        E oldValue = get(elements, index);

        if (oldValue != element) {
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len);
            newElements[index] = element;
            setArray(newElements);
        } else {
            // Not quite a no-op; ensures volatile write semantics
            setArray(elements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}

// 读操作不加锁
private E get(Object[] a, int index) {
    return (E) a[index];
}

在添加元素e完后,再调用setArray(newElements);函数重新赋值,之前指向原容器的引用更改指向当前新容器

HashSet线程安全问题

HashSet底层就是一个HashMap,默认的HashSet是一个初始大小为16,负载因子为0.75的HashMap:

 

HashSet的多线程安全问题实际上就是HashMap的多线程安全问题:
package com.raicho.mianshi.mycollection;

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
 * @author: Raicho
 * @Description:
 * @program: mianshi
 * @create: 2020-07-17 17:03
 *
 * HashSet多线程不安全问题
 * HashSet底层就是HashMap,因此这个案例也是HashMap多线程不安全问题的演示
 */
public class HashSetThreadUnsafe {
    public static void main(String[] args) {
        Set<String> sets = new HashSet<>();
        for (int i = 0; i < 100; ++i) {
            new Thread(() -> {
                sets.add(UUID.randomUUID().toString().substring(0, 4));
                System.out.println(sets);
            },String.valueOf(i)).start();
        }
    }
}

 解决方案:

  • Collections集合类的static方法SynchronizedSet
  • CopyOnWriteArraySet:也是写时复制思想,但是内部还是使用CopyOnWriteArrayList实现:
public class CopyOnWriteArraySet<E> extends AbstractSet<E>
        implements java.io.Serializable {
    private static final long serialVersionUID = 5457747651344034263L;

    private final CopyOnWriteArrayList<E> al;

    /**
     * Creates an empty set.
     */
    public CopyOnWriteArraySet() {
        // 构造器内部实例化了一个CopyOnWriteArrayList
        al = new CopyOnWriteArrayList<E>();
    }
    // ...
}

HashMap多线程安全的解决方案

相比于HashSet,HashMap除了可以使用Collections集合类的synchronizedMap方法外,还可以使用juc包下ConcurrentHashMap类。

package com.raicho.mianshi.mycollection;

import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @author: Raicho
 * @Description:
 * @program: mianshi
 * @create: 2020-07-17 17:03
 */
public class HashMapThreadUnsafe {
    public static void main(String[] args) {
        Map<String,String> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 100; ++i) {
            new Thread(() -> {
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0, 4));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

以上是关于Java集合多线程安全的主要内容,如果未能解决你的问题,请参考以下文章

Java并发多线程编程——集合类线程不安全之HashMap的示例及解决方案

Java并发多线程编程——集合类线程不安全之HashSet的示例及解决方案

Java并发多线程编程——集合类线程不安全之ArrayList的示例及解决方案

Java集合多线程安全

JAVA并发七(多线程环境中安全使用集合API)

Java面试宝典线程安全问题|线程死锁的出现|线程安全的集合类