一、集合的线程安装
1、ArrayList的线程安全问题
ArrayList属于线程不安装,在源码中可以看里面的add(),没有synchronized修饰,在多线程对同一资源的ArrayList集合,进行读和写时会出现异常。
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
2、ArrayList的线程安全问题的解决方案
2.1、使用Vector解决
源码中可以看到Vector中的add(),使用同步锁synchronized修饰,从而保证数据的安全性
/**
* 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;
}
2.2、使用Collections中的synchronizedList放法可以解决
2.3、使用JUC包中的CopyOnWriteArrayList(重点)
CopyOnWriteArrayList使用的是写时复制技术:
当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行 Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器
CopyOnWriteArrayList相当于线程安全的ArrayList。和ArrayList一样,它是个可变数组;但是和ArrayList不同的时,它具有以下特性:
(1). 它最适合于具有以下特征的应用程序:List 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突。
(2). 它是线程安全的。
(3) 因为通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove() 等等)的开销很大。
(4). 迭代器支持hasNext(), next()等不可变操作,但不支持可变 remove()等操作。
(5) 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照。
注意:这时候会抛出来一个新的问题,也就是数据不一致的问题。如果写线程还没来得及写会内存,其他的线程就会读到了脏数据。
列子:
package com.study.demo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 线程的 安全问题
* @author marvin
*
*/
public class LockDemo6 {
public static void main(String[] args) {
//List list = new ArrayList<String>(); //线程不安全
//List list = new Vector<String>(); //使用Vector解决
//List listArray = new ArrayList<String>();
//List list = Collections.synchronizedList(listArray); //使用Collections解决
List list = new CopyOnWriteArrayList(); //使用CopyOnWriteArrayList解决
for (int i = 0; i < 10; i++) {
new Thread(() ->{
list.add(UUID.randomUUID().toString());
System.out.println(list);
}, "线程" + i).start();
}
}
}
2、HashSet的线程安全问题
使用CopyOnWriteArraySet解决。
3、HashMap的线程安全问题
使用ConcurrentHashMap解决。
小结(重点)
(1)、线程安全与线程不安全集合
集合类型中存在线程安全与线程不安全的两种,常见例如:
ArrayList ----- Vector
HashMap -----HashTable
但是以上都是通过synchronized关键字实现,效率较低
(2)、Collections构建的线程安全集合
(3)、java.util.concurrent并发包下
CopyOnWriteArrayList CopyOnWriteArraySet类型,通过动态数组与线程安全个方面保证线程安全