The interviewer asked me how I look at the source code, and I directly talked to him about how to look at the source code for two hours!

Preface

Analyzing the source code can cultivate the ability to think independently (the ability to read the source code to find problems). The most important thing is that we no longer need to buy paper books to learn data structures. The applications of data structures are all in the source code, just like that It’s the same as the well-known "nutrition is in the soup." When we read the theoretical knowledge of the data structure over and over again and still can’t remember where it is used, we may just look at the source code and add a little bit of our own thinking. Know its usage scenarios, after the answer, pay attention to the official account: Kylin changes the bug, and receive more study notes.

What are the benefits of analyzing source code?

In fact, for the small partners who have worked for a period of time, we are all oriented to business development, that is, the addition, deletion, and modification of the programmer/program ape after the meal is discussed. Of course, some people say it like this, "api adjust package man" "It's a bit too much. In fact, for me, I can't bear such words, because adding, deleting, modifying and checking is a common operation, which meets the "28 principle". In fact, programmers/programmers are all working An indispensable part of the enterprise development and application is also an important part of enterprise development and application. Analyzing the source code may bring obvious internal improvement. The second is that the process of analyzing the source code is a manifestation of learning from excellent people. After all, the source code It hides the master's many years of experience and thoughts.

How to analyze the source code?

Throughout the reading process of the article, you must have learned how to analyze the source code and where to start. This is also a subtle process.

This article takes the analysis of the source code of LinkedBlockingQueue as an example, and introduces how to look at the source code!

Second, method analysis

Constructor

The interviewer asked me how I look at the source code, I dumped this article to him

add() method

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;
    }

size() method

The interviewer asked me how I look at the source code, I dumped this article to him

peek() method

The interviewer asked me how I look at the source code, I dumped this article to him

 

contains() method

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();
    }

put() method

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;
    }

take() method

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;
    }

remove() method

 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();
    }

clear() method

The interviewer asked me how I look at the source code, I dumped this article to him

 

toArray() method

 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();
    }

Method summary

Here I did not analyze the poll() method and offer() method of the queue, because the analysis process of it and the take() and add() methods are too the same. As for the little difference, you can go and see the source code for yourself. Ha, I won't introduce too much here due to space issues. In fact, the analysis of the entire method is based on the operation of linked lists and arrays. However, there is no time complexity in the analysis process of the method. However, you will know how to analyze it after reading the vector source code. Pay attention to the official account: Kylin fix bugs and receive more study notes.

Guess you like

Origin blog.csdn.net/QLCZ0809/article/details/112549593