Java并发包——线程安全的Collection相关类
摘要:本文主要学习了Java并发包下线程安全的Collection相关的类。
部分内容来自以下博客:
https://www.cnblogs.com/skywang12345/p/3498483.html
https://www.cnblogs.com/skywang12345/p/3498652.html
https://www.cnblogs.com/skywang12345/p/3503458.html
https://www.cnblogs.com/skywang12345/p/3498995.html
分类
参照之前在学习集合时候的分类,可以将JUC下有关Collection相关的类进行分类。
CopyOnWriteArrayList:实现了List接口,相当于线程安全的ArrayList。
CopyOnWriteArraySet:继承于AbstractSet类,相当于线程安全的HashSet。CopyOnWriteArraySet内部包含一个CopyOnWriteArrayList对象,它是通过CopyOnWriteArrayList实现的。
ConcurrentSkipListSet:继承于AbstractSet类,相当于线程安全的TreeSet。ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的。
ArrayBlockingQueue:继承于AbstractQueue类,是数组实现的线程安全的有界的阻塞队列。
LinkedBlockingQueue:继承于AbstractQueue类,是单向链表实现的(指定大小)阻塞队列,该队列按FIFO(先进先出)排序元素。
LinkedBlockingDeque:继承于AbstractQueue类,是双向链表实现的(指定大小)双向并发阻塞队列,该阻塞队列同时支持FIFO和FILO两种操作方式。
ConcurrentLinkedQueue:继承于AbstractQueue类,是单向链表实现的无界队列,该队列按FIFO(先进先出)排序元素。
ConcurrentLinkedDeque:继承于AbstractQueue类,是双向链表实现的无界队列,该队列同时支持FIFO和FILO两种操作方式。
CopyOnWriteArrayList
说明
CopyOnWriteArrayList的内部有个“volatile数组”来保持数据。在“添加/修改/删除”数据时,都会新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给“volatile数组”,这就是它叫做CopyOnWriteArrayList的原因。CopyOnWriteArrayList就是通过这种方式实现的动态数组,不过正由于它在“添加/修改/删除”数据时,都会新建数组,所以涉及到修改数据的操作,CopyOnWriteArrayList效率很低,但是单单只是进行遍历查找的话,效率比较高。
CopyOnWriteArrayList是通过“volatile数组”来保存数据的。一个线程读取volatile数组时,总能看到其它线程对该volatile变量最后的写入,就这样,通过volatile提供了“读取到的数据总是最新的”这个机制的
保证。
CopyOnWriteArrayList通过互斥锁来保护数据。在“添加/修改/删除”数据时,会先“获取互斥锁”,再修改完毕之后,先将数据更新到“volatile数组”中,然后再“释放互斥锁”,这样,就达到了保护数据的目的。
使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照。迭代器支持hasNext()、next()等不可变操作,但不支持add()、remove()等可变操作。
构造方法:
1 public CopyOnWriteArrayList() { 2 setArray(new Object[0]); 3 } 4 5 public CopyOnWriteArrayList(E[] toCopyIn) { 6 setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); 7 } 8 9 public CopyOnWriteArrayList(Collection<? extends E> c) { 10 Object[] elements; 11 if (c.getClass() == CopyOnWriteArrayList.class) 12 elements = ((CopyOnWriteArrayList<?>)c).getArray(); 13 else { 14 elements = c.toArray(); 15 // c.toArray might (incorrectly) not return Object[] (see 6260652) 16 if (elements.getClass() != Object[].class) 17 elements = Arrays.copyOf(elements, elements.length, Object[].class); 18 } 19 setArray(elements); 20 }
获取和设置array的方法
array是被volatile和transient修饰的一个数组。
关于volatile关键字,我们知道“volatile能让变量变得可见”,即对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。正在由于这种特性,每次更新了“volatile数组”之后,其它线程都能看到对它所做的更新。
关于transient关键字,它是在序列化中才起作用,transient变量不会被自动序列化。
1 private transient volatile Object[] array; 2 3 final Object[] getArray() { 4 return array; 5 } 6 7 final void setArray(Object[] a) { 8 array = a; 9 }
添加元素
因为array数组是volatile修饰的,不能保证线程安全,所以在添加元素时使用锁来保证线程安全。
又因为array数组是volatile修饰的,所以在调用了setArray()方法后,能保证其它线程都能看到新添加的元素。
1 public void add(int index, E element) { 2 // 使用锁来保证线程安全。 3 final ReentrantLock lock = this.lock; 4 lock.lock(); 5 try { 6 // 获得array指向的引用地址。 7 Object[] elements = getArray(); 8 int len = elements.length; 9 // 如果指定位置越界,则抛出异常。 10 if (index > len || index < 0) 11 throw new IndexOutOfBoundsException("