1、ArraryList相关
ArrayList是线程不安全的,在多线程下同时操作一个集合会出java.util.ConcurrentModificationException异常(并发修改异常),如下所示:
public static void main(String[] args) throws IOException { List<String> list = new ArrayList<>(); for (int i = 0; i < 30; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0, 8)); System.out.println(list); }, "thread" + i).start(); } }
解决办法:① 、使用List<String> list = new Vector<>();
②、 使用List<String> list = Collections.synchronizedList(new ArrayList<>());
③、 使用List<String> list = new CopyOnWriteArrayList<>();
Vector线程安全原因:
/** * Appends the specified element to the end of this Vector. * * @param e element to be appended to this Vector * @return {@code true} (as specified by {@link Collection#add}) * @since 1.2 */ public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }
CopyOnWriteArrayList线程安全原因:
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return {@code true} (as specified by {@link Collection#add}) */ 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(); } }
ReentrantLock和synchronized 区别:https://baijiahao.baidu.com/s?id=1648624077736116382&wfr=spider&for=pc
ArrayList生僻知识点:
①、创建时可以自己指定长度,不指定默认长度为10,每次扩容以当前长度0.5倍进行扩容,第一次10,第二次15
②、ArrayList底层是数组结构,所以不能通过forEache和增强for循环去进行删除(迭代器方式)和新增
③、可以通过正常for循环去操作for(int i=0;i<list.size();i++),虽然此方法不报错,但是不推荐(涉及到数组下标移动问题)
④、可通过临时集合将待删除数据记录,待循环结束后,使用removeAll()一起删除
提示:Vectore初始默认长度也为10,但是每次扩容都是按1倍进行扩容,第一次10,第二次20
2、HashSet相关
HashSet为线程不安全的,在多线程下同时操作一个集合也会出java.util.ConcurrentModificationException异常(并发修改异常),实例如下:
public static void main(String[] args) throws IOException { Set<String> list = new HashSet<>(); for (int i = 0; i < 30; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0, 8)); System.out.println(list); }, "thread" + i).start(); } }
解决办法:
①、使用Set<String> list = Collections.synchronizedSet(new HashSet<>());
②、使用Set<String> list = new CopyOnWriteArraySet<>();
CopyOnWriteArraySet的底层依旧是采用和CopyOnWriteArrayList,如下所示:
/** * Creates an empty set */ public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList<E>(); }
HashSet生僻知识点:
①、HashSet底层是通过HashMap进行实现的,故此HashMap也会有线程安全问题
②、HashSet的add方法底层就是调用的HashMap的put方法,只不过value全部都是一个常量对象private static final Object PRESENT = new Object();
③、HashSet的元素全部都是作为HashMap的key值,所以自定义HashSet元素相等方法时必须要重写HashCode方法
3、HashMap相关
HashMap跟HashSet一样,因为HashSet底层就是采用HashMap,所以也会有线程安全的问题
解决办法:
①、使用Map<String, Object> objectObjectMap = Collections.synchronizedMap(new HashMap<>());
②、使用Map<Object, Object> objectObjectConcurrentHashMap = new ConcurrentHashMap<>();
③、使用Map<Object, Object> hashtable = new Hashtable<>();
Hashtable和Vector相似,都是jdk1.0的产物,都是采用了synchronized关键字进行枷锁,所以都是线程安全,但是效率稍微低一点
ConcurrentHashMap则是采用对链表进行分段加锁实现现在安全,效率比HashTable高很多,推荐使用
HashMap生僻知识点:
①、HashMap底层是数组+链表结构实现
②、创建一个HashMap是默认数组长度为16(必须是2的幂),每次扩容时以之前的2倍进行扩容,第一次16,第二次48
③、HashMap的初始容量可以自己指定,一版要求都是以2的n次幂的形式存在的
结论:
HashMap计算添加元素的位置时,使用的位运算,这是特别高效的运算;另外,HashMap的初始容量是2的n次幂,扩容也是2倍的形式进行扩容,是因为容量是2的n次幂,可以使得添加的元素均匀分布在HashMap中的数组上,减少hash碰撞,避免形成链表的结构,使得查询效率降低!
为什么HashMap的容量是2的n次幂?
HashMap的容量为什么是2的n次幂,和这个(n - 1) & hash的计算方法有着千丝万缕的关系,符号&是按位与的计算,这是位运算,计算机能直接运算,特别高效,按位与&的计算方法是,只有当对应位置的数据都为1时,运算结果也为1,当HashMap的容量是2的n次幂时,(n-1)的2进制也就是1111111***111这样形式的,这样与添加元素的hash值进行位运算时,能够充分的散列,使得添加的元素均匀分布在HashMap的每个位置上,减少hash碰撞