高并发场景下ArrayList的线程安全问题

ArrayList是我们常用的数据结构,在并发场景下是线程不安全的。

在读多写少的场景下,我们一般会用读写锁 ReadWriteLock来保证共享对象的线程安全性。

public Object read() {
 lock.readLock().lock();
 // 对ArrayList读取
 lock.readLock().unlock();
}

public void write() {
 lock.writeLock().lock();
 // 对ArrayList写
 lock.writeLock().unlock();
}

这里能解决部分问题。但是还是存在当有一个线程在write的时候,其他读线程会被阻塞,假如这时候有大量的读请求访问,这时候都会被阻塞,这时候我们怎么办?

 CopyOnWrite 思想解决问题

很简单,顾名思义,利用“CopyOnWrite”的方式,这个英语翻译成中文,大概就是“写数据的时候利用拷贝的副本来执行

高并发场景下ArrayList的线程安全问题_第1张图片

这里最关键的就是,在写线程修改完数据后,读线程能访问到最新数据。

这时候需要配合关键字 volatile

// 这个数组是核心的,因为用volatile修饰了
// 只要把最新的数组对他赋值,其他线程立马可以看到最新的数组 
private transient volatile Object[] array;
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;      
// 然后把副本数组赋值给volatile修饰的变量
setArray(newElements);       
return true;
} 
finally{
lock.unlock();
}
}

这里 ReentrantLock lock = this.lock;保证只有一个线程对数据进行修改,而更新操作不会影响线程的读。

private E get(Object[] a, intindex) {   
// 最简单的对数组进行读取   
return(E) a[index];
}

总结:CopyOnWriteArrayList,就是用空间换时间,更新的时候基于副本更新,避免锁,然后最后用volatile变量来赋值保证可见性,更新的时候对读线程没有任何的影响!

 在kafka中写数据,就是利用了copyOnWrite的思想。会把消息先写入客户端本地的内存缓冲,然后在内存缓冲里形成一个Batch之后再一次性发送到Kafka服务器上去,这样有助于提升吞吐量

你可能感兴趣的:(Java基础)