Prefácio
Analisar o código-fonte pode cultivar a capacidade de pensar de forma independente (a capacidade de ler o código-fonte para encontrar problemas). O mais importante é que não precisamos mais comprar livros de papel para aprender estruturas de dados. As aplicações de estruturas de dados são todas no código-fonte, assim A conhecida frase "nutrição está na sopa" é a mesma. Quando lemos o conhecimento teórico da estrutura de dados repetidamente e ainda não conseguimos lembrar onde ela é usada, podemos basta olhar o código-fonte e acrescentar um pouco do nosso próprio pensamento Conheça seus cenários de uso, após a resposta, preste atenção no relato oficial: Kylin altera o bug e receba mais notas de estudo.
Quais são os benefícios de analisar o código-fonte?
Na verdade, para os pequenos parceiros que trabalharam por um período de tempo, estamos todos orientados para o desenvolvimento de negócios, ou seja, a adição, exclusão e modificação do programador / macaco do programa após a refeição é discutida. as pessoas falam assim, "api ajustar pacote man" "É um pouco demais. Na verdade, para mim, eu não suporto essas palavras, porque adicionar, excluir, modificar e verificar é uma operação comum, ou seja, satisfaz o "princípio 28". Na verdade, os programadores / programadores estão todos trabalhando Uma parte indispensável do desenvolvimento e da aplicação da empresa também é uma parte importante do desenvolvimento e da aplicação da empresa. A análise do código-fonte pode trazer melhorias internas óbvias. A segunda é que o processo de análise do código-fonte é uma manifestação do aprendizado de excelentes pessoas, afinal, o código-fonte esconde os muitos anos de experiência e pensamentos do mestre.
Como analisar o código fonte?
Ao longo do processo de leitura do artigo, você deve ter aprendido a analisar o código-fonte e por onde começar.Também é um processo sutil.
Este artigo pega a análise do código-fonte de LinkedBlockingQueue como um exemplo e apresenta como olhar para o código-fonte!
Em segundo lugar, análise do método
Construtor
método add ()
public boolean add(E e) {
//添加到队列的末尾
addLast(e);
return true;
}
//第二步
public void addLast(E e) {
//若添加失败,则直接抛出队列已满的异常信息,给与提示
if (!offerLast(e))
throw new IllegalStateException("Deque full");
}
//第三步
public boolean offerLast(E e) {
//若添加的元素e为null,则直接抛出空指针异常,因为不允许添加元素为null的情况
if (e == null) throw new NullPointerException();
//构造一个元素为e的节点node
Node<E> node = new Node<E>(e);
final ReentrantLock lock = this.lock;
//进行加锁操作
lock.lock();
try {
//进行第四步操作
return linkLast(node);
} finally {
//进行释放锁,当然了,这里你要记住锁释放是放到finally语句块里面的(重要)
lock.unlock();
}
}
//第四步
private boolean linkLast(Node<E> node) {
// assert lock.isHeldByCurrentThread();
//如果队列里元素个数大于等于了队列的容量,说明此时不能再将元素放入队列里面了,直接返回false即可
if (count >= capacity)
return false;
//创建一个临时变量l,将最后一个节点的引用赋值给l
Node<E> l = last;
//将最后一个节点的引用赋值给新节点node的前一个引用(链表的特点)
node.prev = l;
//将新节点node赋值给最后一个节点(因为元素如队列是放在队列的末尾的,队列的特点->先进先出)
last = node;
//为什么这里要判断first是否为null呢?因为添加时不知道队列里是否已经存在元素,若first为null,说明队列里没有元素
if (first == null)
//此时的node就是第一个结点,赋值即可
first = node;
else
//将新节点node挂在原有结点的下一个,即l.next=node
l.next = node;
//队列元素个数进行加一
++count;
//发送一个信号,队列不满的信号
notEmpty.signal();
//默认将元素e添加到队列里面了~,即返回true
return true;
}
método size ()
método peek ()
método contains ()
public boolean contains(Object o) {
//引入队列里不允许放入null,所以若元素o为null,则直接返回false,相当于进行了前置校验操作
if (o == null) return false;
//第二步
fullyLock();
try {
//循环遍历队列的每个节点node的元素item是否与元素o相等,若存在相等元素,则包含,返回true即可
for (Node<E> p = head.next; p != null; p = p.next)
if (o.equals(p.item))
return true;
return false;
} finally {
//第四步,第三次说明了,释放锁要放在finally语句块里面,确保锁可以得到正确的释放
fullyUnlock();
}
}
/**
* Locks to prevent both puts and takes.
*/
//第二步,上面的注释说明,进行加锁操作,禁止进行添加元素到队列,禁止进行从队列里取元素,下面就慢慢分析take()方法了
void fullyLock() {
putLock.lock();
takeLock.lock();
}
//第四步,因为加锁和释放锁是成对的,所以最后一定要记得释放锁哈~,即加了什么锁,要对应的解锁
/**
* Unlocks to allow both puts and takes.
*/
void fullyUnlock() {
takeLock.unlock();
putLock.unlock();
}
método put ()
public void put(E e) throws InterruptedException {
//这个队列是不允许添加空元素的,先来个前置校验,元素为null,则抛出NPE异常
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<E> node = new Node<E>(e);
//获取putLock这把锁引用
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
//当队列元素个数等于队列容量capacity时,进行等待,这里存在一个阻塞操作
while (count.get() == capacity) {
notFull.await();
}
//第二步操作,入队列
enqueue(node);
//元素个数增加1,这里使用的是cas机制
c = count.getAndIncrement();
if (c + 1 < capacity)
//进行信号的通知,和notify一样
notFull.signal();
} finally {
//释放锁操作
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
//第二步,入队列操作(队列的特点是先进先出)
private void enqueue(Node<E> node) {
//将新元素结点node挂在原有队列最后一个元素的后面,然后将最后一个节点的引用赋值给last
last = last.next = node;
}
método take ()
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
//获取takeLock锁引用
final ReentrantLock takeLock = this.takeLock;
//这把锁是可以中断的
takeLock.lockInterruptibly();
try {
//若队列元素个数为0,说明队列里没元素了,此时需要进行发送一个等待的通知
while (count.get() == 0) {
notEmpty.await();
}
//进行从队列里进行取元素操作,见下面的第二步操作
x = dequeue();
//队列元素个数进行减一操作
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
// 释放锁
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
//第二步操作
private E dequeue() {
//下面的操作就是队首元素出来了,队列的后面元素要前移,如果这一步不是很好理解的话,可以按照下面的方式进行debug看下
//在分析这块时,自己也有所成长,因为debug是可以看到元素在数据流中如何处理的
Node<E> h = head;
Node<E> first = h.next;
h.next = h; // help GC
head = first;
//获取队首元素x
E x = first.item;
//触发gc机制进行垃圾回收,什么是垃圾对象呢,就是不可达对象,不了解的可以看下jvm对应的机制
first.item = null;
//返回队列的队首元素
return x;
}
método remove ()
public boolean remove(Object o) {
//这个队列里不允许添加元素为null的元素,所以这里在删除的时候做了一下前置校验
if (o == null) return false;
//第二步,禁止入队列和出队列操作,和上文的contains()方法采取的策略一致
fullyLock();
try {
//循环遍历队列的每个元素,进行比较,这里是不是和你写业务逻辑方法一样,啧啧,有点意思吧
//这里你就明白为什么要看源码了,以及看源码你能得到什么
for (Node<E> trail = head, p = trail.next;
p != null;
trail = p, p = p.next) {
//此时找到待删除的元素o
if (o.equals(p.item)) {
//进行第三步操作
unlink(p, trail);
return true;
}
}
return false;
} finally {
fullyUnlock();
}
}
//第二步,禁止入队列,出队列操作
void fullyLock() {
putLock.lock();
takeLock.lock();
}
//第三步
void unlink(Node<E> p, Node<E> trail) {
//触发gc机制,将垃圾对象进行回收
p.item = null;
//将待删除元素的下一个节点【挂载】待删除元素的前一个元素的next后面
trail.next = p.next;
//判断待删除的元素是否是队列的最后一个元素,如果是,则trail赋值给last,这里你可以想象一下链表的删除操作
if (last == p)
last = trail;
//队列的元素个数减一
if (count.getAndDecrement() == capacity)
notFull.signal();
}
método clear ()
método toArray ()
public Object[] toArray() {
//禁止put/take操作,所以进行加锁,看下面的第二步含义
fullyLock();
try {
//获取队列元素个数size
int size = count.get();
//创建大小为size的object数组,之所以为Object类型是因为Object对象的范围最大,什么类型都可以装下
Object[] a = new Object[size];
int k = 0;
//循环遍历队列的每一个元素,将其装入到数组object里面
for (Node<E> p = head.next; p != null; p = p.next)
a[k++] = p.item;
return a;
} finally {
//最后进行释放对应的锁,其实这里你也可以学到很多东西的,比如说加锁,解锁操作,为啥要放到finally语句块呢等等等
//都会潜移默化的指导你编码的过程
fullyUnlock();
}
}
//第二步,注释已经很好的说明了这个方法的含义,就是阻止put和take操作的,所以进行获取对应的锁进行加锁操作
/**
* Locks to prevent both puts and takes.
*/
void fullyLock() {
putLock.lock();
takeLock.lock();
}
Resumo do método
Aqui, eu não analisei o método poll () e o método offer () da fila, porque o processo de análise dele e os métodos take () e add () são muito iguais. Quanto à pequena diferença, você pode ir para veja o código-fonte por si mesmo Ha, não vou introduzir muito aqui devido a questões de espaço.Na verdade, a análise de todo o método é baseada na operação de listas e arrays vinculados. Porém, não há complexidade de tempo no processo de análise do método, mas você saberá analisá-lo depois de ler o código-fonte do vetor. Preste atenção na conta oficial: Kylin corrige bugs e recebe mais notas de estudo.