Java并发编程与技术内幕:CopyOnWriteArrayList、CopyOnWriteArraySet源码解析

林炳文Evankaka原创作品。转载请注明出处http://blog.csdn.net/evankaka
摘要:本文主要讲了Java中CopyOnWriteArrayList 、CopyOnWriteArraySet的源码分析

一、CopyOnWriteArrayList源码分析

CopyOnWriteArrayList在java的并发场景中用得其实并不是非常多,因为它并不能完全保证读取数据的正确性。其主要有以下的一些特点:
1、适合场景读多写少
2、不能保证读取数据一定是正确 的,因为get时是不加锁的
3、add、remove会加锁再来操作

下面来看看源码:
包含的数据结构

public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
/** 重入锁*/
transient final ReentrantLock lock = new ReentrantLock();
/** 真正存放数据的结构*/
private volatile transient Object[] array;

其实它的数据结构非常简单,就是一个数组和一个重入锁。

构造函数有三个:

/**
* 构造函数,创建一个空数组
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}

/**
* 构造函数,从集合中来初始化列表
*/
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements = c.toArray();
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);//将elements数组内容都转换成Object类型
setArray(elements);
}

/**
* 构造函数,从数组来初始化列表
*/
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));//注意,都向上转型为Object类型
}

主要方法:

1、add时加锁

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);//将当前elements复制到一个长度加1的数组中
   newElements[len] = e;//设置最后一个元素为e
   setArray(newElements);//设置当前元素数组,在这里如果执行get时就有可能会出错
   return true;
} finally {
   lock.unlock();//释放锁
}

这里在注意这个方法:

addIfAbsent这个方法CopyOnWriteArraySet会用到(其实CopyOnWriteArraySet就是封装了一个CopyOnWriteArrayList,下文会说到)

 //如果已存在就不放入
 public boolean addIfAbsent(E e) {
	final ReentrantLock lock = this.lock;
	lock.lock();//取得锁
	try {
	    Object[] elements = getArray();
	    int len = elements.length;
	    Object[] newElements = new Object[len + 1];
	    for (int i = 0; i < len; ++i) { //一个一个元素取出来进行对比
		if (eq(e, elements[i]))
		    return false; // 跳出,list中已存在此元素
		else
		    newElements[i] = elements[i]; //一个一个拷贝到新数组
	    }
	    newElements[len] = e;
	    setArray(newElements);//设置新数组
	    return true;//加入成功
	} finally {
	    lock.unlock();//释放锁
	}
    }

2、删除时在加锁
删除方法一

public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();//取得锁
try {
   Object[] elements = getArray();
   int len = elements.length;
   Object oldValue = elements[index];//查到要删除的元素
   int numMoved = len - index - 1;
   if (numMoved == 0)//删除的刚好是最后一个元素
     setArray(Arrays.copyOf(elements, len - 1));//直接一次拷贝就可以完成
   else {
     Object[] newElements = new Object[len - 1];
     System.arraycopy(elements, 0, newElements, 0, index);//前半部分拷贝
     System.arraycopy(elements, index + 1, newElements, index,numMoved);//后半部分拷贝
     setArray(newElements);//重新设置数组
   }
   return (E)oldValue;//返回删除的元素
} finally {
   lock.unlock();//释放锁
}
}

删除方法二

    public boolean remove(Object o) {
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
	    Object[] elements = getArray();
	    int len = elements.length;
	    if (len != 0) {
		int newlen = len - 1;
		Object[] newElements = new Object[newlen];//设置新数组大小

		for (int i = 0; i < newlen; ++i) {
		    if (eq(o, elements[i])) {
			// found one;  copy remaining and exit
			for (int k = i + 1; k < len; ++k)//找到一个,那么把后面的依次循环拷贝到新数组就完成此次操作
			newElements[k-1] = elements[k];
			setArray(newElements);//设置新数组
			return true;
		    } else
			newElements[i] = elements[i];//没有找到相等的元素
		}

		if (eq(o, elements[newlen])) {//判断是后一个是否相等
		    setArray(newElements);//设置新数组
		    return true;
		}
	    }
	    return false;//如果到这里,表明不包含这个元素,原数组的内容不做改变
	} finally {
	    lock.unlock();
	}
    }

3、get时不加锁

public E get(int index) {
return (E)(getArray()[index]);//直接取得数组对应的索引上的数据就返回了,这里在取得有可能原数组在取完成后就发生改变
}

final Object[] getArray() {
return array;
}

4、contains时不加锁

public boolean contains(Object o) {
Object[] elements = getArray();
return indexOf(o, elements, 0, elements.length) >= 0;
}

private static int indexOf(Object o, Object[] elements,
int index, int fence) {
if (o == null) {
for (int i = index; i < fence; i++)
if (elements[i] == null)
return i;
} else {
for (int i = index; i < fence; i++)
if (o.equals(elements[i]))
return i;
}
return -1;
}

二、CopyOnWriteArraySet源码分析

CopyOnWriteArraySet的原理场景和CopyOnWriteArrayList一样,只不过是不能有重复的元素放入,它里面包含一个CopyOnWriteArrayList,真正调用的方法都 是CopyOnWriteArrayList的方法

数据结构:

public class CopyOnWriteArraySet<E> extends AbstractSet<E> implements java.io.Serializable {
private static final long serialVersionUID = 5457747651344034263L;

private final CopyOnWriteArrayList<E> al; //只包含有一个CopyOnWriteArrayList
。。
}

可以看到就只有一个CopyOnWriteArrayList一个成员变量

其主要方法:

1、add方法

public boolean add(E e) {
return al.addIfAbsent(e); //调用CopyOnWriteArrayList的addIfAbsent方法,如果数据重复返回false,不加入
}

2、remove方法

public boolean remove(Object o) {
return al.remove(o); //调用CopyOnWriteArrayList 的remove方法
}

3、CopyOnWriteArraySet没有get方法,只能通过Iterator来依次取

public Iterator<E> iterator() {
return al.iterator();//调用CopyOnWriteArrayList的iterator()方法
}

而CopyOnWriteArrayList的iterator()方法如下:

public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}

其中COWIterator是CopyOnWriteArrayList的一个内部类,其主要数据结构如下:

private static class COWIterator<E> implements ListIterator<E> {
//数组
private final Object[] snapshot;
//当前指针指向数组地址
private int cursor;

private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
。。。。。
}

时间: 2024-10-20 16:27:24

Java并发编程与技术内幕:CopyOnWriteArrayList、CopyOnWriteArraySet源码解析的相关文章

Java并发编程与技术内幕:聊聊锁的技术内幕(上)

林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 一.基础知识 在Java并发编程里头,锁是一个非常重要的概念.就如同现实生活一样,如果房子上了锁.别人就进不去.Java里头如果一段代码取得了一个锁,其它地方再想去这个锁(或者再执行这个相同的代码)就都得等待锁释放.锁其实分成非常多.比如有互斥锁.读写锁.乐观锁.悲观锁.自旋锁.公平锁.非公平锁等.包括信号量其实都可以认为是一个锁. 1.什么时需要锁呢? 其实非常多的场景,如共享实例变量.共

Java并发编程与技术内幕:线程池深入理解

林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 摘要: 本文主要讲了Java当中的线程池的使用方法.注意事项及其实现源码实现原理,并辅以实例加以说明,对加深Java线程池的理解有很大的帮助. 首先,讲讲什么是线程池?照笔者的简单理解,其实就是一组线程实时处理休眠状态,等待唤醒执行.那么为什么要有线程池这个东西呢?可以从以下几个方面来考虑:其一.减少在创建和销毁线程上所花的时间以及系统资源的开销 .其二.2将当前任务与主线程隔离,能实现和主

Java并发编程与技术内幕:ArrayBlockingQueue、LinkedBlockingQueue及SynchronousQueue源码解析

林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 摘要:本文主要讲了Java中BlockingQueue的源码 一.BlockingQueue介绍与常用方法 BlockingQueue是一个阻塞队列.在高并发场景是用得非常多的,在线程池中.如果运行线程数目大于核心线程数目时,也会尝试把新加入的线程放到一个BlockingQueue中去.队列的特性就是先进先出很容易理解,在java里头它的实现类主要有下图的几种,其中最常用到的是ArrayBl

Java并发编程与技术内幕:聊聊锁的技术内幕(中)

摘要:本文主要讲了读写锁. 一.读写锁ReadWriteLock 在上文中回顾了并发包中的可重入锁ReentrantLock,并且也分析了它的源码.从中我们知道它是一个单一锁(笔者自创概念),意思是在多人读.多人写.或同时有人读和写时.只能有一个人能拿到锁,执行代码.但是在很多场景.我们想控制它能多人同时读,但是又不让它多人写或同时读和写时.(想想这是不是和数据库的可重复读有点类型?),这时就可以使用读写锁:ReadWriteLock. 下面来看一个应用 package com.lin; imp

Java并发编程与技术内幕:Callable、Future、FutureTask、CompletionService

林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 在上一文章中,笔者介绍了线程池及其内部的原理.今天主要讲的也是和线程相关的内容.一般情况下,使用Runnable接口.Thread实现的线程我们都是无法返回结果的.但是如果对一些场合需要线程返回的结果.就要使用用Callable.Future.FutureTask.CompletionService这几个类.Callable只能在ExecutorService的线程池中跑,但有返回结果,也可

Spark技术内幕:Worker源码与架构解析

首先通过一张Spark的架构图来了解Worker在Spark中的作用和地位: Worker所起的作用有以下几个: 1. 接受Master的指令,启动或者杀掉Executor 2. 接受Master的指令,启动或者杀掉Driver 3. 报告Executor/Driver的状态到Master 4. 心跳到Master,心跳超时则Master认为Worker已经挂了不能工作了 5. 向GUI报告Worker的状态 说白了,Worker就是整个集群真正干活的.首先看一下Worker重要的数据结构: v

SPRING技术内幕,Spring源码深度解析

 SPRING技术内幕,Spring源码深度解析 SPRING技术内幕:深入解析SPRING架构与设计原理(第2版)[带书签].pdf: http://www.t00y.com/file/78131650 Spring源码深度解析 [郝佳编著] sample.pdf: http://www.t00y.com/file/78131634 [jingshuishenliu.400gb.com]Spring Data.pdf: http://www.t00y.com/file/78256084 [

Java集合干货系列-(一)ArrayList源码解析

前言 今天来介绍下ArrayList,在集合框架整体框架一章中,我们介绍了List接口,ArrayList继承了AbstractList,实现了List.ArrayList在工作中经常用到,所以要弄懂这个类是极其重要的.构造图如下:蓝色线条:继承绿色线条:接口实现 正文 ArrayList简介 ArrayList定义 public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomA

【转】Java 集合系列11之 Hashtable详细介绍(源码解析)和使用示例

概要 前一章,我们学习了HashMap.这一章,我们对Hashtable进行学习.我们先对Hashtable有个整体认识,然后再学习它的源码,最后再通过实例来学会使用Hashtable.第1部分 Hashtable介绍第2部分 Hashtable数据结构第3部分 Hashtable源码解析(基于JDK1.6.0_45)第4部分 Hashtable遍历方式第5部分 Hashtable示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3310887.h