集合框架的对比以及集合的线程安全化
1.集合和集合框架
其实是我自己有些整不明白的,通常集合指的是Collaction接口下的接口或者实现类
集合框架目前我的印象中指的是Collaction接口下和Map接口下的接口和实现类
2.Collection:
1):List接口下:
①:ArrayList:动态数组
②:LinkedList:链表
③:Vector:动态数组
④:相同点和区别:
ArrayList和Vector:同:动态数组
继承自AbstractList抽象类
实现了List(List集合接口),RandomAccess(实现随机访问),Cloneable(克隆接口), Serializable(序列化)接口
异:Vector的方法都是是线程安全的,
ArrayList不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。
LinkedList的方法跟ArrayList一样,也不是线程安全的
ArrayList和LinkedList:同:实现了List,Cloneable,Serializable接口
都不是线程安全的
异:ArrayList是动态数组,继承自AbstractList抽象类,实现了RandomAccess接口,可以进行随机访问,查找操作循序,增删操作慢
LinkedList是链表,继承自AbstrackSequentialList抽象类,实现了Deque接口,只能顺序访问,查找操作慢,增删操作快
ps:注意增删过程和增删操作:增删整个过程中,由于需要先查找再增删,所以在整个过程中时间复杂度是一样的
(关于时间复杂度后续有时间会写一点,
现在简单来讲就是:数组查询时间复杂度为O(1),复制(增删过程)时间复杂度为O(n);
链表查询时间复杂度为O(n),增删过程时间复杂度为O(1))
2)Set接口下:
HashSet:底层是HashMap,继承自AbstractSet抽象类,实现了Set,Cloneable,serializable接口
EntrySet:常见于HashMap,开发者无法new,继承自AbstractSet抽象类,常用作便利HashMap的键值对
3)List接口下的集合和Set接口下的集合的区别:List下的集合是有序可重复集合,Set下的集合是无序不可重复集合
3.Map:链表和数组的结合体
1)HashMap:数组方式存储key,value构成的Entry对象(来自:https://blog.csdn.net/gldemo/article/details/44653787),
继承自AbstractMap抽象类,实现了Map,Cloneable,Serializable接口
2)Hashtable:继承自Dictionary抽象类,实现了Map,Cloneable,Serializable接口
3)相同点和区别:(继承的抽象类和实现接口不再赘述)
HashMap:允许有一个key值为null和任意多个value值为null,是非线程安全的,遍历使用的是Iterator
Hashtable:的key和value都不允许有null,而且是线程安全的,遍历使用的是Enumeration。
HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。
所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,
但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。
但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
(关于迭代器未曾仔细学习,来自:https://blog.csdn.net/sinat_35821285/article/details/80225116)
4)和HashSet的比较:(继承的抽象类和实现接口不再赘述)
HashMap:存储键值对
HashSet:存储对象(可以认为是值)
5)TreeSet和TreeMap不常用,所以这两者的联系和区别不做描述,兴趣来了可以
参考:https://blog.csdn.net/sinat_35821285/article/details/80225116
https://blog.csdn.net/gldemo/article/details/44653787
4.线程安全
1)工作原理:
①:jvm有一个main memory,而每个线程有自己的working memory(线程的工作区)
②:一个线程对一个variable(线程共享数据)进行操作时,都要在自己的working memory里面建立一个copy,操作完之后再写入main memory。
③:多个线程同时操作同一个variable,就可能会出现不可预知的结果。根据上面的解释,很容易想出相应的scenario。
④:而用synchronized的关键是建立一个monitor,这个monitor可以是要修改的variable也可以其他你认为合适的object
⑤:通过给这个monitor加锁来实现线程安全,每个线程在获得这个锁之后,要执行完load到working memory -> use&assign -> store到main memory的过程,
才会释放它得到的锁。这样就实现了所谓的线程安全。
2)简图:
ps:以下内容来自:https://www.cnblogs.com/lzhdonald/archive/2020/07/17/13332727.html
3)集合框架的线程安全:
线程不安全集合 | 线程安全集合 |
ArrayList | Vector |
LinkedList | |
HashMap | Hashtable |
HashSet | |
TreeMap | |
TreeSet |
4)解决方案:
① ArrayList:
-
-
- 使用Vector(效率很低);
- 使用Collections集合工具类的static方法synchronizedList,将ArrayList的集合传入Collections.synchronizedList(List list)方法,使其成为线程安全的集合(原理未学习):内部直接将接受的List对象传递给静态内部类SynchronizedList对象,然后Collections.synchronizedList(new ArrayList<>())返回的List对象的调用方法都是直接调用输入List对象的方法,但是加了synchronized,类似装饰器模式,也是对输入List的一种增强,源码如下:
1 static <T> List<T> synchronizedList(List<T> list, Object mutex) { 2 return (list instanceof RandomAccess ? 3 new SynchronizedRandomAccessList<>(list, mutex) : 4 new SynchronizedList<>(list, mutex)); 5 } 6 7 static class SynchronizedList<E> 8 extends SynchronizedCollection<E> 9 implements List<E> { 10 private static final long serialVersionUID = -7754090372962971524L; 11 12 final List<E> list; 13 14 SynchronizedList(List<E> list) { 15 super(list); 16 this.list = list; 17 } 18 SynchronizedList(List<E> list, Object mutex) { 19 super(list, mutex); 20 this.list = list; 21 } 22 23 public boolean equals(Object o) { 24 if (this == o) 25 return true; 26 synchronized (mutex) {return list.equals(o);} 27 } 28 public int hashCode() { 29 synchronized (mutex) {return list.hashCode();} 30 } 31 32 public E get(int index) { 33 synchronized (mutex) {return list.get(index);} 34 } 35 public E set(int index, E element) { 36 synchronized (mutex) {return list.set(index, element);} 37 } 38 public void add(int index, E element) { 39 synchronized (mutex) {list.add(index, element);} 40 } 41 public E remove(int index) { 42 synchronized (mutex) {return list.remove(index);} 43 } 44 45 public int indexOf(Object o) { 46 synchronized (mutex) {return list.indexOf(o);} 47 } 48 public int lastIndexOf(Object o) { 49 synchronized (mutex) {return list.lastIndexOf(o);} 50 } 51 52 public boolean addAll(int index, Collection<? extends E> c) { 53 synchronized (mutex) {return list.addAll(index, c);} 54 } 55 56 public ListIterator<E> listIterator() { 57 return list.listIterator(); // Must be manually synched by user 58 } 59 60 public ListIterator<E> listIterator(int index) { 61 return list.listIterator(index); // Must be manually synched by user 62 } 63 64 public List<E> subList(int fromIndex, int toIndex) { 65 synchronized (mutex) { 66 return new SynchronizedList<>(list.subList(fromIndex, toIndex), 67 mutex); 68 } 69 } 70 71 @Override 72 public void replaceAll(UnaryOperator<E> operator) { 73 synchronized (mutex) {list.replaceAll(operator);} 74 } 75 @Override 76 public void sort(Comparator<? super E> c) { 77 synchronized (mutex) {list.sort(c);} 78 } 79 80 private Object readResolve() { 81 return (list instanceof RandomAccess 82 ? new SynchronizedRandomAccessList<>(list) 83 : this); 84 } 85 }
- 使用CopyOnWriteArrayList,读写分离的思想读写分离的思想,在并发读的时候不需要加锁,因为它能够保证并发读的情况下不会添加任何元素。而在并发写的情况下,需要先加锁,但是并不直接对当前容器进行写操作。而是先将当前容器进行复制获取一个新的容器,进行完并发写操作之后,当之前指向原容器的引用更改指向当前新容器。也就是说,并发读和并发写是针对不同集合,因此不会产生并发异常,源码如下:
1 // CopyOnWriteArrayList.java 2 public boolean add(E e) { 3 // 写操作加锁 4 final ReentrantLock lock = this.lock; 5 lock.lock(); 6 try { 7 // 原有容器复制一份 8 Object[] elements = getArray(); 9 int len = elements.length; 10 // 创建一个容器,将原来的数据复制到新容器中,并且还有一个位置空余 11 Object[] newElements = Arrays.copyOf(elements, len + 1); 12 // 将新元素添加到空余位置 13 newElements[len] = e; 14 // 将原来指向旧容器的引用指向新容器 15 setArray(newElements); 16 return true; 17 } finally { 18 // 写操作完成,解锁 19 lock.unlock(); 20 } 21 } 22 23 public E set(int index, E element) { 24 // 更新操作类似 25 final ReentrantLock lock = this.lock; 26 lock.lock(); 27 try { 28 Object[] elements = getArray(); 29 E oldValue = get(elements, index); 30 31 if (oldValue != element) { 32 int len = elements.length; 33 Object[] newElements = Arrays.copyOf(elements, len); 34 newElements[index] = element; 35 setArray(newElements); 36 } else { 37 // Not quite a no-op; ensures volatile write semantics 38 setArray(elements); 39 } 40 return oldValue; 41 } finally { 42 lock.unlock(); 43 } 44 } 45 46 // 读操作不加锁 47 private E get(Object[] a, int index) { 48 return (E) a[index]; 49 }
-
②HashSet:
-
-
- 使用Collections集合工具类的static方法SynchronizedSet,将HashSet的集合传入Collections.synchronizedSet(Set set)方法,使其成为线程安全的集合;
- CopyOnWriteArraySet:也是写时复制思想,但是内部还是使用CopyOnWriteArrayList实现,源码如下:
1 public class CopyOnWriteArraySet<E> extends AbstractSet<E> 2 implements java.io.Serializable { 3 private static final long serialVersionUID = 5457747651344034263L; 4 5 private final CopyOnWriteArrayList<E> al; 6 7 /** 8 * Creates an empty set. 9 */ 10 public CopyOnWriteArraySet() { 11 // 构造器内部实例化了一个CopyOnWriteArrayList 12 al = new CopyOnWriteArrayList<E>(); 13 } 14 // ... 15 }
-
③HashMap:
-
-
- 使用Collections集合工具类的静态方法synchronizedMap,将HashMap的集合传入Collections.synchronizedMap(Map map)方法,使其成为线程安全的Map
- 使用juc包下ConcurrentHashMap类
-
TreeSet和TreeMap异同参考:
https://blog.csdn.net/gldemo/article/details/44653787
https://blog.csdn.net/sinat_35821285/article/details/80225116
线程安全测试代码参考:
https://www.cnblogs.com/lzhdonald/archive/2020/07/17/13332727.html