java并发编程之美-阅读记录5
java并发包中的并发List
5.1CopeOnWriteArrayList
并发包中的并发List只有CopyOnWriteArrayList,该类是一个线程安全的arraylist,对其进行的修改操作都是在底层的一个复制数组上进行的,也就是使用了写时复制策略。
该类的结构:
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8673264195747942595L;
// 可重入的独占锁,用来保证对arraylist的修改操作,同一时间只有一个线程
final transient ReentrantLock lock = new ReentrantLock();
// 存放对象的底层数组 内存可见性
private transient volatile Object[] array;
// 基于硬件的原子操作了Unsafe
private static final sun.misc.Unsafe UNSAFE;
// 锁的偏移量
private static final long lockOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = CopyOnWriteArrayList.class;
lockOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("lock"));
} catch (Exception e) {
throw new Error(e);
}
}
}
问题:
何时初始化list,初始化list的大小是多少,list是有限大小吗? copyonwriteArraylist是无界数组
如何保证线程安全,比如多个线程进行读写时如何保证是线程安全的?
如何保证迭代器遍历list时的数据一致性?
5.2源码分析
1、初始化
构造函数:
// 空的list
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
// 入参为Collection类型的集合,该构造会将集合中的元素复制到list中
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
elements = c.toArray();
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
setArray(elements);
}
// 入参为泛型数组,将数组复制到list的底层数组中
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
2、添加元素add方法
// 添加元素,在list的末尾添加
public boolean add(E e) {
final ReentrantLock lock = this.lock;
// 获取独占锁
lock.lock();
try {
// 获取copyOnWriteArrayList底层数组
Object[] elements = getArray();
int len = elements.length;
// 复制一份新的数组,比原数组大一,所以CopyOnWriteArraylist是一个无界数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 将要添加的元素放到新数组的最后位置
newElements[len] = e;
// 使用新数组替换原来的数组
setArray(newElements);
return true;
} finally {
// 操作完毕后,释放独占锁
lock.unlock();
}
}
3、获取元素,此时就会产生写时复制的弱一致性问题:当线程a获取到地层数组后,但是没有执行get(Object[] a,int index)方法,此时线程b操作该集合,删除了一个元素(删除的是复制出的新数组中的数据,同时会使用新数组覆盖旧数组),name线程a继续获取执行位置的数据,此时底层数组仍然是之前没有删除数据的数组,这样就产生了弱一致性问题。
// 直接获取底层数组指定索引位置的数据
private E get(Object[] a, int index) {
return (E) a[index];
} // 获取指定索引出的值 getArray方法返回底层数组
public E get(int index) {
return get(getArray(), index);
}
4、修改指定位置的元素
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
// 获取独占锁
lock.lock();
try {
// 获取底层数组
Object[] elements = getArray();
// 根据索引获取要修改的值
E oldValue = get(elements, index);
// 如果新值不等于旧值时,就会复制一份数组,将新值设置进入,然后用新数组覆盖就数组
if (oldValue != element) {
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
// 旧数组重新覆盖旧数组--> 这一步虽然没有改变数组,但是为了保证volatitle语义,仍然会重新设置一次
setArray(elements);
}
return oldValue;
} finally {
// 释放锁
lock.unlock();
}
}
5、删除元素
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// 获取要删除索引位置的元素
E oldValue = get(elements, index);
// 计算要移动的元素的数量
int numMoved = len - index - 1;
// 要移动的元素为0,则代表删除的是最后一个元素
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
// 要删除的不是最后一个,则新建一个len-1长度的数组
Object[] newElements = new Object[len - 1];
// 同时以要删除的索引index为分界线,复制0-index,index+1 - len的元素
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
6、弱一致性的迭代器
所谓弱一致性是指:返回迭代器后,其他线程对list的操作(增删改)对迭代器是不可见的。
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
} // 可以看到迭代器中的方法,不能增删改,相应的方法会抛出异常
static final class COWIterator<E> implements ListIterator<E> {
/** Snapshot of the array */
private final Object[] snapshot;
/** Index of element to be returned by subsequent call to next. */
private int cursor; private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
} public boolean hasNext() {
return cursor < snapshot.length;
} public boolean hasPrevious() {
return cursor > 0;
} @SuppressWarnings("unchecked")
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
} @SuppressWarnings("unchecked")
public E previous() {
if (! hasPrevious())
throw new NoSuchElementException();
return (E) snapshot[--cursor];
} public int nextIndex() {
return cursor;
} public int previousIndex() {
return cursor-1;
} public void remove() {
throw new UnsupportedOperationException();
} public void set(E e) {
throw new UnsupportedOperationException();
} public void add(E e) {
throw new UnsupportedOperationException();
} @Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
Object[] elements = snapshot;
final int size = elements.length;
for (int i = cursor; i < size; i++) {
@SuppressWarnings("unchecked") E e = (E) elements[i];
action.accept(e);
}
cursor = size;
}
}
弱一致性:
package com.nxz.blog.otherTest; import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList; public class TestThread003 { private static CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>(); /**
* 测试CopyOnWriteArrayList的弱一致性问题
* 如果输出结果是:test1 test2 test3 test4 则说明该类具有弱一致性
*/
public static void main(String[] args) throws InterruptedException { copyOnWriteArrayList.add("test1");
copyOnWriteArrayList.add("test2");
copyOnWriteArrayList.add("test3");
copyOnWriteArrayList.add("test4"); Thread t = new Thread(new Runnable() {
@Override
public void run() {
copyOnWriteArrayList.add("runnabl1");
copyOnWriteArrayList.add("runnabl2");
copyOnWriteArrayList.add("runnabl3");
}
}); Iterator<String> iterator = copyOnWriteArrayList.iterator(); // 等待线程执行完毕
t.join(); while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
结果:
test1
test2
test3
test4
java并发编程之美-阅读记录5的更多相关文章
- java并发编程之美-阅读记录1
1.1什么是线程? 在理解线程之前先要明白什么是进程,因为线程是进程中的一个实体.(线程是不会独立存在的) 进程:是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程中的 ...
- java并发编程之美-阅读记录11
java并发编程实践 11.1ArrayBlockingQueue的使用 有关logback异步日志打印中的ArrayBlockingQueue的使用 1.异步日志打印模型概述 在高并发.高流量并且响 ...
- java并发编程之美-阅读记录2
2.1什么是多线程并发编程 并发:是指在同一时间段内,多个任务同时在执行,并且执行没有结束(同一时间段又包括多个单位时间,也就是说一个cpu执行多个任务) 并行:是指在单位时间内多个任务在同时执行(也 ...
- java并发编程之美-阅读记录10
同步器 10.1CountDownLatch 在开发过程中经常会遇到在主线程中开启多个子线程去并行执行任务,并且主线程需要等待子线程执行完毕后在进行汇总.在CountDownLatch出现之前使用线程 ...
- java并发编程之美-阅读记录7
java并发包中的并发队列 7.1ConcurrentLinkedQueue 线程安全的无界非阻塞队列(非阻塞队列使用CAS非阻塞算法实现),其底层数组使用单向列表实现,对于出队和入队操作使用CAS非 ...
- java并发编程之美-阅读记录6
java并发包中锁 6.1LockSupport工具类 该类的主要作用就是挂起和唤醒线程,该工具类是创建锁和其他工具类的基础.LockSupport类与每个使用他的线程都关联一个许可证,在默认情况下调 ...
- java并发编程之美-阅读记录4
java并发包中的原子操作类,这些类都是基于非阻塞算法CAS实现的. 4.1原子变量操作类 AtomicInteger/AtomicLong/AtomicBoolean等原子操作类 AtomicLon ...
- java并发编程之美-阅读记录3
java并发包中的ThreadLocalRandom类,jdk1.7增加的随机数生成器 Random类的缺点:是多个线程使用同一个原子性的种子变量,导致对原子变量的更新产生竞争,降低了效率(该类是线程 ...
- Java并发编程之美之并发编程线程基础
什么是线程 进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程的一个执行路径,一个进程至少有一个线程,进程的多个线程共享进程的资源. java启动main函数其实就 ...
随机推荐
- 条款2:尽量使用const ,enum,inline替换define
宁可使用编译器而不用预处理器 假设我们使用预处理器: #define ABC 1.56 这标识符ABC也许编译器没看到,也许它在编译器处理源码前就被预处理器移走了,于是“标识符”ABC没有进入标识符列 ...
- 【Leetcode周赛】从contest-81开始。(一般是10个contest写一篇文章)
Contest 81 (2018年11月8日,周四,凌晨) 链接:https://leetcode.com/contest/weekly-contest-81 比赛情况记录:结果:3/4, ranki ...
- 用Matlab的.m脚本文件处理实验室数据
找到相应的文件 findfile %1 打开文件夹 %2 拷贝第一个文件 %3 关闭当前文件,再次拷贝新的文件,直到文件末尾结束 clc clear DST_PATH_t = 'C:\Users\Ma ...
- jmeter 参数化2_CSV Data Set Config
CSV Data Set Config:适用于参数取值范围较大的时候使用,该方法具有更大的灵活性 操作路径:HTTP取样器-->Add-->Config Element-->CSV ...
- 18.Vim基础指令(自用)——2019年12月13日
title: vim study date: "2018-12-26 20:17:16" tags: 指令学习 categories: 技术驿站 vim study 2018年12 ...
- CNN笔记:通俗理解卷积神经网络
CNN笔记:通俗理解卷积神经网络 2016年07月02日 22:14:50 v_JULY_v 阅读数 250368更多 分类专栏: 30.Machine L & Deep Learning 机 ...
- mysql图形化管理工具workbench下载安装以及基本使用
1.下载安装 去mysql官网下载地址进行下载安装 2. 创建schema和表格等基本操作 (1)连接数据库 打开workbench,操作如下: ps:正常需要输入mysql的密码的,但是我之前保存了 ...
- 小程序 css 文字溢出,长度过长用 。。。
word-break: break-all;/*允许在单词内换行*/ text-align: left; /* line-height: 45rpx; */ text-overflow: -o-ell ...
- thinkphp 相关
路径重写,既模式2 需要.htaccess文件放到index.php同级, http.config文件 中当前项目目录配置 AllowOverride All LoadModule rewrite_ ...
- python的列表 元组 字典
列表和元组都是序列,是数据元素的集合,数据元素可以是数值.字符串,布尔值.对象等. 一.列表:用方括号定义[] 空列表 names = [] 带值的列表 names = ["bill&quo ...