JDK容器类List,Set,Queue源码解读
List,Set,Queue都是继承Collection接口的单列集合接口。List常用的实现主要有ArrayList,LinkedList,List中的数据是有序可重复的。Set常用的实现主要是HashSet,Set中的数据是无序不可重复的。Queue常用的实现主要有ArrayBlockingQueue,LinkedBlockingQueue,Queue是一个保持先进先出的顺序队列,不允许随机访问队列中的元素。
ArrayList核心源码解读
ArrayList是一个底层用数组实现的集合,数组元素类型为Object类型,支持随机访问,元素有序且可以重复,它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。
当通过 ArrayList() 构造一个是集合,它是构造了一个空数组,初始长度为0。当第1次添加元素时,会创建一个长度为10的数组,并将该元素赋值到数组的第一个位置,当添加的元素大于10的时候,数组会进行第一次扩容。扩容1.5倍,长度变为15。
ArrayList在遍历的时候时不能修改的,即遍历的时候不能增加或者删除元素,否则会抛ConcurrentModificationException
ArrayList是线程不安全的。
ArrayList源码中的主要字段
// 默认数组的大小
private static final int DEFAULT_CAPACITY = 10;
// 默认空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 实际存放数据的数组
private transient Object[] elementData;
ArrayList源码中的构造器
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity]; //将自定义的容量大小当成初始化elementData的大小
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
ArrayList源码中的add方法
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); //添加元素之前,首先要确定集合的大小
elementData[size++] = e; //在数据中正确的位置上放上元素e,并且size++
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 如果为空数组
return Math.max(DEFAULT_CAPACITY, minCapacity); // 返回默认的我数组长度
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++; // 修改次数+1,相当于版本号
// overflow-conscious code
if (minCapacity - elementData.length > 0) // 如果实际容量小于需要容量
grow(minCapacity); // 扩容
}
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; // 拿到数组的当前长度
int newCapacity = oldCapacity + (oldCapacity >> 1); //新数组的长度等于原数组长度的1.5倍
if (newCapacity - minCapacity < 0) //当新数组长度仍然比minCapacity小,则为保证最小长度,新数组等于minCapacity
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) //当得到的新数组长度比 MAX_ARRAY_SIZE大时,调用 hugeCapacity 处理大数组
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity); //调用 Arrays.copyOf将原数组拷贝到一个大小为newCapacity的新数组(拷贝引用)
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE; // 数组的最大长度为Integer.MAX_VALUE
}
ArrayList源码中的get方法
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
rangeCheck(index); //判断索引合法性
return elementData(index);
}
线程安全的ArrayList—CopyOnWriteArrayList
CopyOnWriteArrayList是基于写时复制(copy-on-write)思想来实现的一个线程安全的ArrayList集合类。它实现了List接口,内部持有一个ReentrantLock来给写上锁,底层是用volatile transient声明的数组array,它是读写分离的,写入数据时会复制出一个新的数组并加上ReentrantLock锁,完成写入后将新数组赋值给当前array,而读操作不需要获得锁,支持并发。
CopyOnWriteArrayList的写时复制导致了数据并不是实时的,有一定的延迟性,同时由于数据的复制,当数据量非常大的时候会占用很大的内存。
CopyOnWriteArrayList是适合读多写少的场景。
CopyOnWriteArrayList核心源码解读
// 存放数据的数组
private transient volatile Object[] array;
/**
* 添加数据方法
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#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); // 拷贝数组到新数组
newElements[len] = e; // 将元素赋值到新数组的末尾
setArray(newElements); //设置新数组为当前数组
return true;
} finally {
lock.unlock(); // 解锁
}
}
HashSet源码解读
HashSet是最常用的Set实现,它继承了AbstractSet抽象类,实现了Set,Cloneable和java.io.Serializable接口。
HashSet中存储的是无序不可重复的数据,他的底层的数据存储是通过HashMap实现的,HashSet将数据存储在HashMap的key中,将HashMap的value设为一个Object引用。
HashSet核心源码解读
// 实际存储数据的HashMap
private transient HashMap<E,Object> map;
// HashMap的value引用
private static final Object PRESENT = new Object();
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>(); //new一个 HashMap 对象出来,采用无参的 HashMap 构造函数,具有默认初始容量(16)和加载因子(0.75)。
}
/**
* Adds the specified element to this set if it is not already present.
* More formally, adds the specified element <tt>e</tt> to this set if
* this set contains no element <tt>e2</tt> such that
* <tt>(e==null ? e2==null : e.equals(e2))</tt>.
* If this set already contains the element, the call leaves the set
* unchanged and returns <tt>false</tt>.
*
* @param e element to be added to this set
* @return <tt>true</tt> if this set did not already contain the specified
* element
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
线程安全的HashSet—CopyOnWriteArraySet
CopyOnWriteArraySet是一个线程安全的HashSet,它底层是通过CopyOnWriteArrayList实现的,它是通过在添加数据的时候如果数据不存在才进行添加来实现了数据的不可重复
CopyOnWriteArraySet 核心源码解读
// 实际存放数据
private final CopyOnWriteArrayList<E> al;
/**
* Adds the specified element to this set if it is not already present.
* More formally, adds the specified element {@code e} to this set if
* the set contains no element {@code e2} such that
* <tt>(e==null ? e2==null : e.equals(e2))</tt>.
* If this set already contains the element, the call leaves the set
* unchanged and returns {@code false}.
*
* @param e element to be added to this set
* @return {@code true} if this set did not already contain the specified
* element
*/
public boolean add(E e) {
return al.addIfAbsent(e); // 如果不存在则添加
}
Queue详细分析
Queue是先入先出(FIFO)的一个队列数据结构,可以分为阻塞队列和非阻塞队列,Queue接口与List、Set同一级别,都是继承了Collection接口。
Queue API
方法 | 作用 | 描述 |
---|---|---|
add | 增加一个元素 | 如果队列已满,则抛出一个IIIegaISlabEepeplian异常 |
remove | 移除并返回队列头部的元素 | 如果队列为空,则抛出一个NoSuchElementException异常 |
element | 返回队列头部的元素 | 如果队列为空,则抛出一个NoSuchElementException异常 |
offer | 添加一个元素并返回true | 如果队列已满,则返回false |
poll | 移除并返问队列头部的元素 | 如果队列为空,则返回null |
peek | 返回队列头部的元素 | 如果队列为空,则返回null |
put | 添加一个元素 | 如果队列满,则阻塞 |
take | 移除并返回队列头部的元素 | 如果队列为空,则阻塞 |
ArrayBlockingQueue源码解读
ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列。
// 存放数据的数组
final Object[] items;
// 取数据索引
int takeIndex;
// 放数据索引
int putIndex;
// 数据量
int count;
// 锁
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
/** items index for next put, offer, or add */
int putIndex;
/**
* Inserts the specified element at the tail of this queue if it is
* possible to do so immediately without exceeding the queue's capacity,
* returning {@code true} upon success and {@code false} if this queue
* is full. This method is generally preferable to method {@link #add},
* which can fail to insert an element only by throwing an exception.
*
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) { // offer方法是非阻塞
checkNotNull(e);
final ReentrantLock lock = this.lock; // offer的时候加锁
lock.lock();
try {
if (count == items.length) // 如果没有空间, 返回false
return false;
else {
enqueue(e); // 如果有空间,入队列
return true;
}
} finally {
lock.unlock();
}
}
/**
* Inserts the specified element at the tail of this queue, waiting
* for space to become available if the queue is full.
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock; // 加锁
lock.lockInterruptibly();
try {
while (count == items.length) // queue的容量已满
notFull.await(); // 阻塞
enqueue(e);
} finally {
lock.unlock();
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await(); //队列为空时,将使这个线程进入阻塞状态,直到被其他线程唤醒时取出元素
return dequeue(); //消费对头中的元素
} finally {
lock.unlock();
}
}
LinkedBlockingQueue源码解读
LinkedBlockingQueue底层是采用链表实现的一个阻塞的线程安全的队列。
LinkedBlockingQueue构造的时候若没有指定大小,则默认大小为Integer.MAX_VALUE,当然也可以在构造函数的参数中指定大小。
LinkedBlockingQueue中采用两把锁,取数据的时候加takeLock,放数据的时候加putLock。
// 容量
private final int capacity;
// 元素数量
private final AtomicInteger count = new AtomicInteger();
// 链表头
transient Node<E> head;
// 链表尾
private transient Node<E> last;
// take锁
private final ReentrantLock takeLock = new ReentrantLock();
// 当队列无元素时,take锁会阻塞在notEmpty条件上,等待其它线程唤醒
private final Condition notEmpty = takeLock.newCondition();
// 放锁
private final ReentrantLock putLock = new ReentrantLock();
// 当队列满了时,put锁会会阻塞在notFull上,等待其它线程唤醒
private final Condition notFull = putLock.newCondition();
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE); // 如果没传容量,就使用最大int值初始化其容量
}
/**
* Inserts the specified element at the tail of this queue, waiting if
* necessary for space to become available.
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock; // 使用putLock锁加锁
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
/*
* Note that count is used in wait guard even though it is
* not protected by lock. This works because count can
* only decrease at this point (all other puts are shut
* out by lock), and we (or some other waiting put) are
* signalled if it ever changes from capacity. Similarly
* for all other uses of count in other wait guards.
*/
while (count.get() == capacity) { // 如果队列满了,就阻塞在notFull条件上
notFull.await(); // 等待被其它线程唤醒
}
enqueue(node); // 队列不满了,就入队
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock; // 使用takeLock加锁
takeLock.lockInterruptibly();
try {
while (count.get() == 0) { // 如果队列无元素,则阻塞在notEmpty条件上
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
ConcurrentLinkedQueue源码解读
ConcurrentLinkedQueue是线程安全的无界非阻塞队列,其底层数据结构是使用单向链表实现,入队和出队操作是使用我们经常提到的CAS来保证线程安全的。
ConcurrentLinkedQueue不允许插入的元素为null。
private transient volatile Node<E> head;
private transient volatile Node<E> tail;
private static final sun.misc.Unsafe UNSAFE;
private static final long headOffset;
private static final long tailOffset;
/**
* Inserts the specified element at the tail of this queue.
* As the queue is unbounded, this method will never return {@code false}.
*
* @return {@code true} (as specified by {@link Queue#offer})
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
checkNotNull(e); // 为null则抛出空指针异常
final Node<E> newNode = new Node<E>(e);
for (Node<E> t = tail, p = t;;) { // 自旋
Node<E> q = p.next;
if (q == null) { // 如果q==null说明p是尾节点,则执行插入
// p is last node
if (p.casNext(null, newNode)) { // 使用CAS设置p节点的next节点
// Successful CAS is the linearization point
// for e to become an element of this queue,
// and for newNode to become "live".
if (p != t) // hop two nodes at a time
casTail(t, newNode); // Failure is OK.
return true;
}
// Lost CAS race to another thread; re-read next
}
else if (p == q)
// We have fallen off list. If tail is unchanged, it
// will also be off-list, in which case we need to
// jump to head, from which all live nodes are always
// reachable. Else the new tail is a better bet.
p = (t != (t = tail)) ? t : head;
else
// Check for tail updates after two hops.
p = (p != t && t != (t = tail)) ? t : q;
}
}
JDK容器类List,Set,Queue源码解读的更多相关文章
- JDK并发基础与部分源码解读
之前写的一个ppt 搬到博客来
- JVM 源码解读之 CMS 何时会进行 Full GC
t点击上方"涤生的博客",关注我 转载请注明原创出处,谢谢!如果读完觉得有收获的话,欢迎点赞加关注. 前言 本文内容是基于 JDK 8 在文章 JVM 源码解读之 CMS GC 触 ...
- JDK容器类Map源码解读
java.util.Map接口是JDK1.2开始提供的一个基于键值对的散列表接口,其设计的初衷是为了替换JDK1.0中的java.util.Dictionary抽象类.Dictionary是JDK最初 ...
- ScheduledThreadPoolExecutor源码解读
1. 背景 在之前的博文--ThreadPoolExecutor源码解读已经对ThreadPoolExecutor的实现原理与源码进行了分析.ScheduledExecutorService也是我们在 ...
- SDWebImage源码解读之SDWebImageDownloaderOperation
第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...
- SDWebImage源码解读之SDWebImageCache(上)
第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...
- SDWebImage源码解读之SDWebImageCache(下)
第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...
- AFNetworking 3.0 源码解读 总结(干货)(下)
承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...
- AFNetworking 3.0 源码解读(八)之 AFImageDownloader
AFImageDownloader 这个类对写DownloadManager有很大的借鉴意义.在平时的开发中,当我们使用UIImageView加载一个网络上的图片时,其原理就是把图片下载下来,然后再赋 ...
随机推荐
- python安装Django常见错误
今天没事安装了一下python的web框架,Django.自己踩了一些雷,记录下来,留给后面的学者们,不要踩同样的雷了. 1.pip版本过低,安装不了,升级pip指令 python -m pip in ...
- 在Winform开发框架中使用DevExpress的TreeList和TreeListLookupEdit控件
DevExpress提供的树形列表控件TreeList和树形下拉列表控件TreeListLookupEdit都是非常强大的一个控件,它和我们传统Winform的TreeView控件使用上有所不同,我一 ...
- 跟我学SpringCloud | 第十四篇:Spring Cloud Gateway高级应用
SpringCloud系列教程 | 第十四篇:Spring Cloud Gateway高级应用 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 ...
- STM32F072从零配置工程-自定义时钟配置详解
从自己的板子STM32F407入手,参考官方的SystemInit()函数: 核心在SetSysClock()这个函数,官方默认是采用HSE(设定为8MHz)作为PLL锁相环的输入输出168MHz的S ...
- Element ui colorpicker在Vue中的使用
首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker> 在 ...
- I/O:FileChannel
FileChannel: abstract void force(boolean metaData) :强制将所有对此通道的文件更新写入包含该文件的存储设备中. abstract MappedByte ...
- 双剑合璧——掌握 cURL 和 Dig 走天涯
如今随着大量的应用转移到网络,作为开发者,会经常做一些通讯测试,例如从网站获取信息.模拟用户向网站提交或者上传数据,查看应用通讯情况等等,现在变成了非常重要的任务. 一起来认识 cURL cURL 是 ...
- golang开发:类库篇(四)配置文件解析器goconfig的使用
为什么要使用goconfig解析配置文件 目前各语言框架对配置文件书写基本都差不多,基本都是首先配置一些基础变量,基本变量里面有环境的配置,然后通过环境变量去获取该环境下的变量.例如,生产环境跟测试环 ...
- 使用DQL查询数据库
DQL ( Data Query Language) 是数据查询语言,如 Select 语句#### 一.DQL 语法 ```sqlSELECT [ALL | DISTINCT]{* | table. ...
- Java 8 终于支持 Docker!
Java 8曾经与Docker无法很好地兼容性,现在问题已消失. 请注意:我在本文中使用采用GNU GPL v2许可证的OpenJDK官方docker映像.在Oracle Java SE中,这里描述的 ...