java互联网架构 发表于 2020-12-15 12:42:53

面试官问我平时怎么看源码的,我把这篇文章甩给他了

1.1,为什么要分析源码?
分析源码可以培养一下本身独立思考问题的能力(愿意读源码找问题的能力),最紧张的是我们不用再买纸质书去学习数据结构了,数据结构的应用都在源码里面了,正如那句被人熟知的"营养都在汤里面"一样,当我们看过一遍一遍数据结构的理论知识后还是想不起它在那里用到时,可能看一看源码加上本身的一点思考时就知道它的使用场景了,解答完毕。
1.2,分析源码的利益是?
其实,对于工作一段时间的小伙伴来说,我们都是面向业务开发,就是被人饭后谈资的增删改查程序员/程序猿,固然了,有的人说起来这样的话,"api调包侠"就有点过分了,其实对于我个人来说,我是受不了这样的话语的,由于增删改查是常用的操纵,即满足了"二八原则",其实,程序员/程序猿都是工作中不可或缺的一部分,也是企业开发应用中紧张的一环,分析源码可能就会带来显而易见的内功提升,其次就是分析源码的过程中就是在向良好的人学习的一种体现,究竟源码里面隐藏了大师多年的心得和思想。
1.3,如何分析源码?
在整个文章的阅读过程中,想必你已经学会了如何分析源码了,从哪入手,这也是一种潜移默化的过程。
本文以分析LinkedBlockingQueue的源码为例,介绍下应该如何看源码!
二, 方法分析

2.1,构造函数
https://p9.pstatp.com/large/pgc-image/c5d104a6851c4a9b8ca56f7c011f4a7e
2.2,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 node = new Node(e);      final ReentrantLock lock = this.lock;    //进行加锁操纵      lock.lock();      try {            //进行第四步操纵            return linkLast(node);      } finally {   //进行开释锁,固然了,这里你要记住锁开释是放到finally语句块里面的(紧张)            lock.unlock();      }    }//第四步private boolean linkLast(Node node) {      // assert lock.isHeldByCurrentThread();    //假如队列里元素个数大于即是了队列的容量,说明此时不能再将元素放入队列里面了,直接返回false即可      if (count >= capacity)            return false;    //创建一个暂时变量l,将末了一个节点的引用赋值给l      Node 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;    }2.3,size()方法
https://p9.pstatp.com/large/pgc-image/012cd6b924e6468aa4b6eb97ac9729a2
2.4,peek()方法
https://p3.pstatp.com/large/pgc-image/1c84ad5379c24721a371e2f9bcc1259c
2.5,contains()方法
public boolean contains(Object o) {    //引入队列里不答应放入null,所以若元素o为null,则直接返回false,相当于进行了前置校验操纵      if (o == null) return false;    //第二步      fullyLock();      try {    //循环遍历队列的每个节点node的元素item是否与元素o相等,若存在相等元素,则包罗,返回true即可            for (Node 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();    }2.6,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 node = new Node(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 node) {      //将新元素结点node挂在原有队列末了一个元素的后面,然后将末了一个节点的引用赋值给last      last = last.next = node;    }2.7,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 h = head;      Node first = h.next;      h.next = h; // help GC      head = first;   //获取队首元素x      E x = first.item;    //触发gc机制进行垃圾回收,什么是垃圾对象呢,就是不可达对象,不了解的可以看下jvm对应的机制      first.item = null;    //返回队列的队首元素      return x;    }2.8,remove()方法
public boolean remove(Object o) {   //这个队列里不答应添加元素为null的元素,所以这里在删除的时候做了一下前置校验      if (o == null) return false;   //第二步,禁止入队列和出队列操纵,和上文的contains()方法采取的策略一致      fullyLock();      try {      //循环遍历队列的每个元素,进行比力,这里是不是和你写业务逻辑方法一样,啧啧,有点意思吧      //这里你就明白为什么要看源码了,以及看源码你能得到什么            for (Node 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 p, Node trail) {   //触发gc机制,将垃圾对象进行回收      p.item = null;    //将待删除元素的下一个节点【挂载】待删除元素的前一个元素的next后面      trail.next = p.next;    //判断待删除的元素是否是队列的末了一个元素,假如是,则trail赋值给last,这里你可以想象一下链表的删除操纵      if (last == p)            last = trail;    //队列的元素个数减一      if (count.getAndDecrement() == capacity)            notFull.signal();    }2.9,clear()方法
https://p1.pstatp.com/large/pgc-image/a0c51a3e1b11460db26a30dde2b3bfa7
2.10, toArray()方法
public Object[] toArray() {   //禁止put/take操纵,所以进行加锁,看下面的第二步寄义      fullyLock();      try {   //获取队列元素个数size            int size = count.get();   //创建大小为size的object数组,之所以为Object类型是由于Object对象的范围最大,什么类型都可以装下            Object[] a = new Object;            int k = 0;   //循环遍历队列的每一个元素,将其装入到数组object里面            for (Node p = head.next; p != null; p = p.next)                a = p.item;            return a;      } finally {   //末了进行开释对应的锁,其实这里你也可以学到很多东西的,好比说加锁,解锁操纵,为啥要放到finally语句块呢等等等   //都会潜移默化的引导你编码的过程            fullyUnlock();      }    }//第二步,注释已经很好的说明了这个方法的寄义,就是制止put和take操纵的,所以进行获取对应的锁进行加锁操纵   /**   * Locks to prevent both puts and takes.   */    void fullyLock() {      putLock.lock();      takeLock.lock();    }2.11,方法总结
这里本身没有对队列的poll()方法,offer()方法进行分析,是由于它和take(),add()方法的分析过程都太一样了,至于一点点差别,你可以去本身看下源码哈,这里由于篇幅的问题就不过多介绍了,其实整个方法的分析就是基于链表和数组的操纵。不过这里没有在方法的分析过程中去写时间复杂度,不过,你看过vector源码之后就知道如何分析了。
三,总结一下

3.1,思考一下
文章的开头抛出了下面的3个问题


1.1,为什么要分析源码? 那我这里在问下,你分析完这个集合源码,学习到了什么?是不是有点启发?
1.2,分析源码的利益是? 在分析的过程中,你学会了如何更加去编码,或许你没有意识到,但这是潜移默化的过程,相信你学习到了
1.3,如何分析源码? 在整个文章的阅读过程中,想必你已经学会了如何分析源码了,从哪入手,这也是一种潜移默化的过程
页: [1]
查看完整版本: 面试官问我平时怎么看源码的,我把这篇文章甩给他了