CopyOnWriteArrayList实现类,
Java并发包中的并发list,CopyOnWriteArrayList是一个ArrayList的一个线程安全的变体,写数组的拷贝,
对其进行修改操作都是底层 的一个复制数组中进行的,也就是使用写数据时复制策略
写时复制(CopyOnWrite,简称cow)思想计算机涉及领域中的一种优化策略,其核心思想是多个调用者(Callers),同时要求同一个资源(内存,磁盘上的数据存储,cpu),调用者们会获取相同的指针指向相同的资源,直到某一个调用者试图修改资源的内容时,系统才会真正复制一份专用的副本给调用者,
测试
//多线程下迭代器的弱一致性结果
public class MethodGeneric {
//如果多个线程同时操作volatile变量,那么对该变量的写操作必须在读操作之前执行(禁止重排序),并且写操作的结果对读操作可见(强缓存一致性)。
private static volatile CopyOnWriteArrayList<String > copyOnWriteArrayList = new CopyOnWriteArrayList<String>();
public static void main(String[] args) {
copyOnWriteArrayList.add("hello");
copyOnWriteArrayList.add("word");
copyOnWriteArrayList.add("welcome");
copyOnWriteArrayList.add("to");
copyOnWriteArrayList.add("xi`an");
System.out.println("原有数据"+copyOnWriteArrayList);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//修改list结合中索引为4的元素
copyOnWriteArrayList.set(4,"sx");
//删除元素
copyOnWriteArrayList.remove(1);
System.out.println("线程copy的数组"+copyOnWriteArrayList);
}
});
copyOnWriteArrayList.set(4,"sx");
//在保证修改线程之前获取迭代器
Iterator<String> iterator = copyOnWriteArrayList.iterator();
//启动线程
thread.start();
try {
// 等待线程执行完毕
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//迭代器遍历元素
while (iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("修改后的数据"+copyOnWriteArrayList);
}
}
测试结果
输出后的结果,线程操作没有使原有数据进行修改操作,
分析源码
CopyOnWriteArrayList添加方法
添加前先拷贝原有数组到新数组中。再将元素赋值到新数组的末端。然后将新数组复制到原来数组中,保证原有数组的安全性
public boolean add(E e) {
//lock变量被final修饰,赋值后不可被修改,增加了变量的安全性
final ReentrantLock lock = this.lock;
//获取锁
lock.lock();
try {
//获取原有数组
Object[] elements = getArray();
//数组长度赋值到len变量中
int len = elements.length;
//创建新数组,将原来的数组的元素,长度加1拷贝到新数组中,在新数组中操作避免了直接操作原有数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
//将参数中传递的e赋值到newElemnts[]数组的末端
newElements[len] = e;
//最终将数组复制到原来数组中
setArray(newElements);
return true;
} finally {
//释放锁
lock.unlock();
}
}
获取数组
//最终方法,此方法不能被重写
final Object[] getArray() {
//返回数组
return array;
}
设置数组
//最终方法,
final void setArray(Object[] a) {
//数组的赋值
array = a;
}
数组存储
//从源码中可以看出,该方法只能被setArray/getArray方法访问,
/**
* transient : 说明该数组不参与序列化,和反序列化操作,
* volatile : jvm提供的轻量级的同步机制,保证数组的可见性,无原子性,指令禁重排
*/
private transient volatile Object[] array;
CopyOnWriteArrayList修改方法
替换数组中指定位置的元素。
public E set(int index, E element) {
//lock变量被final修饰,赋值后不可被修改,增加了变量的安全性
final ReentrantLock lock = this.lock;
//获取锁
lock.lock();
try {
//获取原有数组
Object[] elements = getArray();
//获取要修改的数组以索引
E oldValue = get(elements, index);
/*
* 判断要修改的元素与数组中的是否一致
* 一致走else语句重新赋值给array
* 不一致则走if语句
*/
if (oldValue != element) {
//数组长度赋值到len变量
int len = elements.length;
//将原有数组即长度拷贝到新数组中,在新数组中操作避免了直接操作原有数组
Object[] newElements = Arrays.copyOf(elements, len);
//对新数组的索引位置进行赋值
newElements[index] = element;
//最终将数组复制到原来数组中
setArray(newElements);
} else {
// 不做任何操作,重新赋值到原来数组中
setArray(elements);
}
return oldValue;
} finally {
//释放锁
lock.unlock();
}
}
获取指定索引
private E get(Object[] a, int index) {
return (E) a[index];
}
附加数组小练习
package com.sin.demo.test.demo;
import java.util.Arrays;
import java.util.Scanner;
/**
* 标题:ArraysDataBase
* 题目内容:在查找的时候可以直接访问原数组,在做改动(添加元素,修改元素,删除元素)先拷贝一份源数组,
* 再在拷贝的数组中进行操作(添加元素,修改元素,删除元素),最后是否覆盖源数据(类似数据库的提交事务:commit)
*
* @author sin
* @date 2022/10/14
* @apiNote
*/
public class ArraysDataBaseDemo {
public static void main(String[] args) {
//从控制台中扫描数据
Scanner scanner = new Scanner(System.in);
byte num = 0;//定义变量,给谁使用do—while()使用的
//创建新数组
String[] newUserData;
//定义原数据
String[] userData = new String[]{"张三、18、123456", "李四、23、123456", "王五、36、123456",
"赵六、32、321321", "唐七、46、123123"};
// //先计算原数组长度,5赋值,
// int userDataLength = userData.length;//5
do {
//循环计算源数组的长度
int userDataLength = userData.length;
System.out.println("--------------------------------ArraysDataBase-----------------------------------");
System.out.println("【1】查看数据");
System.out.println("【2】增加数据");
System.out.println("【3】删除数据");
System.out.println("【4】修改数据");
System.out.println("【5】退出系统");
//选择功能
num = scanner.nextByte();
//1,查看数据-------------------------------------------------------------------------------------------------
//检测控制台中输入的为1时进入if语句
if (num == 1) {
System.out.println("【1】查看数据");
System.out.println("id 姓名 年龄 密码");
//循环遍历userData(原数组)数组的每一个元素
for (int i = 0; i < userDataLength; i++) {
System.out.println((i + 1) + " " + userData[i]);
}
}
//2,增加数据-------------------------------------------------------------------------------------------------
//检测控制中输入的为2时进入if语句
if (num == 2) {
System.out.println("【2】增加数据");
//创建新数组newUserData
newUserData = new String[userDataLength + 1];
//将newUserData数组的长度赋值给newUserDataLength变量中
int newUserDataLength = newUserData.length;
//循环将userData(原数组)的元素依次赋值给newUserData(新数组)中
//如果使用userData(原数组)的的length(长度)会导致下标越界异常出错,因为原数组的长度要比新数组的长度少一个元素位置
for (int i = 0; i < userDataLength; i++) {
newUserData[i] = userData[i];
}
System.out.println("id 姓名 年龄 密码");
//循环遍历复制过后的(newUserData)新数组元素
for (int i = 0; i < newUserDataLength - 1; i++) {
System.out.println((i + 1) + " " + newUserData[i]);
}
System.out.println("请输入要增加的数据");
String insertData = scanner.next();
//给newUserData数组中的最后一个元素赋值
newUserData[newUserDataLength - 1] = insertData;
System.out.println("id 姓名 年龄 密码");
//循环遍历添加元素后的newUserData数组
for (int i = 0; i < newUserDataLength; i++) {
System.out.println((i + 1) + " " + newUserData[i]);
}
System.out.println("是否提交数据");
String commitData = scanner.next();
if (commitData.equals("提交")) {
//将(newUserData)新数组的所有元素以及长度复制给(userData)原数组
//userData = newUserData;
/**
* Arrays : 操作数组用的类
* copyOf : 拷贝数组方法
* newUserData : 被拷贝的数组
* newUserDataLength :将要拷贝的数组长度
* userData : 将要拷贝的数组
*/
userData = Arrays.copyOf(newUserData, newUserDataLength);
System.out.println("提交成功");
} else {
System.out.println("提交失败");
}
}
//3,删除数据-------------------------------------------------------------------------------------------------
if (num == 3) {
System.out.println("【3】删除数据");
//把userData数组中的长度及元素复制给newUserData;
newUserData = userData;
int newUserDataLength = newUserData.length;
System.out.println("id 姓名 年龄 密码");
//遍历userData数组中的元素
for (int i = 0; i < newUserDataLength; i++) {
System.out.println((i + 1) + " " + userData[i]);
}
System.out.println("请输入要删除的id");
//从控制台中获取要删除的变量值,减1:元素对应的个数(索引时从0开始的)
int deleteDataById = (scanner.nextInt() - 1 ) ;//输入的数字 跟要删除的索引 是同一条元素
//如果输入的长度不超过userData数组的长度则进入if语句
if (deleteDataById != 0 && deleteDataById < newUserDataLength) {
userData[deleteDataById] = null;//输入具体的索引并将其赋null值
//从删除的索引开始遍历元素
for (int i = deleteDataById; i < newUserDataLength - 1; i++) {
newUserData[i] = newUserData[i + 1];//将i索引往后的索引往前索引赋值
}
newUserData[newUserDataLength - 1] = null;//将最后一个索引位置的元素赋null值
System.out.println("删除成功");
System.out.println("id 姓名 年龄 密码");
//循环遍历newUserDataLength数组的元素
for (int i = 0; i < newUserDataLength; i++) {
System.out.println((i + 1) + " " + newUserData[i]);
}
System.out.println("是否提交数据");
String commitData = scanner.next();
if (commitData.equals("提交")) {
//把newUserData数组copy给userData数组
userData = Arrays.copyOf(newUserData, newUserDataLength - 1);
System.out.println("提交成功");
} else {
System.out.println("提交失败");
}
} else {
System.out.println("没有id为:【" + (deleteDataById + 1) + "】数据");
}
}
//4,修改数据-------------------------------------------------------------------------------------------------
if (num == 4) {
System.out.println("【4】修改数据");
//将userData数组的元素及长度,一并复制给newUserData
newUserData = userData ;
//将newUserData数组的长度赋值给变量
int newUserDataLength = newUserData.length;
System.out.println("id 姓名 年龄 密码");
//循环遍历newUserDataLength数组的元素
for (int i = 0; i < newUserDataLength; i++) {
System.out.println((i + 1) + " " + newUserData[i]);
}
System.out.println("请输入要修改的id");
int updateData = scanner.nextInt();
System.out.println("请输入您要修改【"+updateData+"】数据的内容");
String update = scanner.next();
newUserData[updateData - 1] = update;
System.out.println("修改成功");
System.out.println("id 姓名 年龄 密码");
//循环遍历newUserDataLength数组的元素
for (int i = 0; i < newUserDataLength; i++) {
System.out.println((i + 1) + " " + newUserData[i]);
}
System.out.println("是否提交数据");
String commitData = scanner.next();
if (commitData.equals("提交")) {
//把newUserData数组copy给userData数组
userData = Arrays.copyOf(newUserData, newUserDataLength);
System.out.println("提交成功");
} else {
System.out.println("提交失败");
}
}
//5,退出程序-------------------------------------------------------------------------------------------------
if (num == 5) {//如果输入5直接退出该方法结束程序
return;
}
} while (num != 0);
}
}