学习课程记录笔记
1、ArrayList
线程安全问题
ArrayList
不是线程安全类,在多线程同时写的情况下,会抛出java.util.ConcurrentModificationException
异常。
private static void listNotSafe() {
List<String> list = new ArrayList<String>();
for (int i = 0; i <30 ; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(Thread.currentThread().getName()+"\t"+list);
},String.valueOf(i)).start();
}
}
解决方法:
- 使用
Vector
(ArrayList
所有方法加synchronized
,太重)。 - 使用
Collections.synchronizedList()
转换成线程安全类。 - 使用
java.concurrent.CopyOnWriteArrayList
(推荐)。
CopyOnWriteArrayList
这是JUC的类,通过写时复制来实现读写分离。比如其add()
方法,就是先复制一个新数组,长度为原数组长度+1,然后将新数组最后一个元素设为添加的元素。
源码分析
/**
* 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();
}
}
ArrayList的初始化与扩容机制:
初始化 源码:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
扩容机制:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
(oldCapacity >> 1); 也就是按照当前容量的一半扩容
2、Set线程安全问题
跟List类似,HashSet
和TreeSet
都不是线程安全的,与之对应的有CopyOnWriteSet
这个线程安全类。这个类底层维护了一个CopyOnWriteArrayList
数组。
源码
/**
* Creates an empty set.
*/
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
3、HashSet和HashMap
HashSet
底层是用HashMap
实现的。既然是用HashMap
实现的,那HashMap.put()
需要传两个参数,而HashSet.add()
只传一个参数,这是为什么?实际上HashSet.add()
就是调用的HashMap.put()
,只不过Value被写死了,是一个private static final Object
对象。
源码
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
4、Map
HashMap
不是线程安全的,Hashtable
是线程安全的,但是跟Vector
类似,太重量级(synchronized修饰)。所以也有类似CopyOnWriteMap,只不过叫ConcurrentHashMap
。
public class ContainerNotSafeDemo {
public static void main(String[] args) throws Exception{
listNotSafe();
listSafe();
setNoSafe();
setSafe();
mapNotSafe();
mapSafe();
}
private static void mapSafe() throws Exception{
TimeUnit.SECONDS.sleep(5);
System.out.println("---------------Map线程安全---------------");
Map<String,String> map = new ConcurrentHashMap<>();
for (int i = 0; i <30 ; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0, 8));
System.out.println(Thread.currentThread().getName()+"\t"+map);
},String.valueOf(i)).start();
}
}
private static void mapNotSafe() {
System.out.println("---------------Map线程不安全---------------");
Map<String,String> map = new HashMap<>();
for (int i = 0; i <30 ; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0, 8));
System.out.println(Thread.currentThread().getName()+"\t"+map);
},String.valueOf(i)).start();
}
}
private static void setSafe()throws Exception {
Set<String> set = new CopyOnWriteArraySet<>();
TimeUnit.SECONDS.sleep(5);
System.out.println("---------------Set线程安全---------------");
for (int i = 0; i <30 ; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(Thread.currentThread().getName()+"\t"+set);
},String.valueOf(i)).start();
}
}
private static void setNoSafe() {
System.out.println("---------------Set线程不安全---------------");
Set<String> set = new HashSet<>();
for (int i = 0; i <30 ; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(Thread.currentThread().getName()+"\t"+set);
},String.valueOf(i)).start();
}
}
private static void listNotSafe() {
System.out.println("---------------List线程不安全---------------");
List<String> list = new ArrayList<String>();
for (int i = 0; i <30 ; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(Thread.currentThread().getName()+"\t"+list);
},String.valueOf(i)).start();
}
}
private static void listSafe()throws Exception {
// List<String> list = new ArrayList<String>();
// List<String> strings = Collections.synchronizedList(list);
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
TimeUnit.SECONDS.sleep(5);
System.out.println("---------------List线程安全---------------");
for (int i = 0; i <30 ; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(Thread.currentThread().getName()+"\t"+list);
},String.valueOf(i)).start();
}
}
}