本文来自作者投稿,原作者:WwpwW

1.1,为什么要分析源码?

分析源码可以培养一下自己独立思考问题的能力(愿意读源码找问题的能力),最重要的是我们不用再买纸质书去学习数据结构了,数据结构的应用都在源码里面了,正如那句被人熟知的"营养都在汤里面"一样,当我们看过一遍一遍数据结构的理论知识后还是想不起它在哪里用到时,可能看一看源码加上自己的一点思考时就知道它的使用场景了,解答完毕。

1.2,分析源码的好处是?

其实,对于工作一段时间的小伙伴来说,我们都是面向业务开发,就是被人饭后谈资的增删改查程序员/程序猿,当然了,有的人说起来这样的话,"api调包侠"就有点过分了,其实对于我个人来说,我是受不了这样的话语的,因为增删改查是常用的操作,即满足了"二八原则",其实,程序员/程序猿都是工作中不可或缺的一部分,也是企业开发应用中重要的一环,分析源码可能就会带来显而易见的内功提升,其次就是分析源码的过程中就是在向优秀的人学习的一种体现,毕竟源码里面隐藏了大师多年的心得和思想。

1.3,如何分析源码?

在整个文章的阅读过程中,想必你已经学会了如何分析源码了,从哪入手,这也是一种潜移默化的过程。

本文以分析LinkedBlockingQueue的源码为例,介绍下应该如何看源码!

二,  方法分析

2.1,构造函数

public LinkedBlockingQueue() {//创建容量为整形最大值this(Integer.MAX_VALUE);
}public LinkedBlockingQueue(int capacity) {//若capacity小于0,则直接抛出参数不合法的异常if (capacity <= 0) throw new IllegalArgumentException();this.capacity = capacity;//默认创建一个元素为null的节点Nodelast = head = new Node<E>(null);
}

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的节点nodeNode<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,将最后一个节点的引用赋值给lNode<E> l = last;//将最后一个节点的引用赋值给新节点node的前一个引用(链表的特点)node.prev = l;//将新节点node赋值给最后一个节点(因为元素如队列是放在队列的末尾的,队列的特点->先进先出)last = node;//为什么这里要判断first是否为null呢?因为添加时不知道队列里是否已经存在元素,若first为null,说明队列里没有元素if (first == null)//此时的node就是第一个结点,赋值即可first = node;else//将新节点node挂在原有结点的下一个,即l.next=nodel.next = node;//队列元素个数进行加一++count;//发送一个信号,队列不满的信号notEmpty.signal();//默认将元素e添加到队列里面了~,即返回truereturn true;}

2.3,size()方法

 public int size() {//直接返回队列里表示元素个数的成员变量count即可return count.get();}

2.4,peek()方法

public E peek() {//若队列元素个数为0,说明队列里还未有元素,直接返回nullif (count.get() == 0)return null;//获取锁final ReentrantLock takeLock = this.takeLock;//进行加锁操作takeLock.lock();try {//获取队列的第一个结点引用firstNode<E> first = head.next;//若队列的第一个节点引用为null,则直接返回null即可if (first == null)return null;else//获取第一个节点引用first的元素item返回return first.item;} finally {//进行释放锁,记得释放锁要放在finally语句块里,确保锁可以得到释放takeLock.unlock();}}

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

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,元素为eNode<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挂在原有队列最后一个元素的后面,然后将最后一个节点的引用赋值给lastlast = 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<E> h = head;Node<E> first = h.next;h.next = h; // help GChead = first;//获取队首元素xE x = first.item;//触发gc机制进行垃圾回收,什么是垃圾对象呢,就是不可达对象,不了解的可以看下jvm对应的机制first.item = null;//返回队列的队首元素return x;}
image-20201122102406286

2.8,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) {//此时找到待删除的元素oif (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();}

2.9,clear()方法

    /*** Atomically removes all of the elements from this queue.* The queue will be empty after this call returns.*/public void clear() {//方法上的注释已经说明了clear()方法的作用是干什么的了,很清晰//在clear()操作时,不允许进行put/take操作,进行了加锁fullyLock();try {//循环遍历队列的每一个元素,将其节点node对应的元素item置为null,触发gcfor (Node<E> p, h = head; (p = h.next) != null; h = p) {h.next = h;p.item = null;}//队首队尾相同表明队列为空了head = last;//将队列的容量capacity置为0if (count.getAndSet(0) == capacity)notFull.signal();} finally {//记得在这里释放锁fullyUnlock();}}

2.10, toArray()方法

 public Object[] toArray() {//禁止put/take操作,所以进行加锁,看下面的第二步含义fullyLock();try {//获取队列元素个数sizeint 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();}

2.11,方法总结

这里自己没有对队列的poll()方法,offer()方法进行分析,是因为它和take(),add()方法的分析过程都太一样了,至于一点点差别,你可以去自己看下源码哈,这里由于篇幅的问题就不过多介绍了,其实整个方法的分析就是基于链表和数组的操作。不过这里没有在方法的分析过程中去写时间复杂度,不过,你看过vector源码之后就知道如何分析了。

三,总结一下

3.1,思考一下

文章的开头抛出了下面的3个问题

1.1,为什么要分析源码? 那我这里在问下,你分析完这个集合源码,学习到了什么?是不是有点启发?

1.2,分析源码的好处是? 在分析的过程中,你学会了如何更加去编码,或许你没有意识到,但这是潜移默化的过程,相信你学习到了

1.3,如何分析源码? 在整个文章的阅读过程中,想必你已经学会了如何分析源码了,从哪入手,这也是一种潜移默化的过程

有道无术,术可成;有术无道,止于术

欢迎大家关注Java之道公众号

好文章,我在看❤️

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

  1. java执行sql文件_面试官问你MyBatis SQL是如何执行的?把这篇文章甩给他

    初识 MyBatis MyBatis 是第一个支持自定义 SQL.存储过程和高级映射的类持久框架.MyBatis 消除了大部分 JDBC 的样板代码.手动设置参数以及检索结果.MyBatis 能够支持 ...

  2. 面试官问你MyBatis SQL是如何执行的?把这篇文章甩给他

    来自:Java建设者 初识 MyBatis MyBatis 是第一个支持自定义 SQL.存储过程和高级映射的类持久框架.MyBatis 消除了大部分 JDBC 的样板代码.手动设置参数以及检索结果.M ...

  3. oracle sql 全是子查询查询速度太慢如何优化_如果面试官问你如何优化mysql分页查询,请把这篇文章甩给他!...

    在我们日常开发中,分页查询是必不可少的,可以说每干后端程序猿大部分时间都是CURD,所以分页的查询也接触的不少,你们都是怎么实现的呢?前不久的一段时间,我的一个同事突然找我寻求帮助,他说他写的sql查 ...

  4. 面试官问你B树和B 树,就把这篇文章丢给他

    原文链接:面试官问你B树和B 树,就把这篇文章丢给他 在看这篇文章之前,我们回顾一下前面的几篇关于MySQL的文章,应该对你读下面的文章有所帮助. InnoDB与MyISAM等存储引擎对比 面试官问你 ...

  5. 面试官问你B树和B+树,就把这篇文章丢给他

    1 B树 在介绍B+树之前, 先简单的介绍一下B树,这两种数据结构既有相似之处,也有他们的区别,最后,我们也会对比一下这两种数据结构的区别. 1.1 B树概念 B树也称B-树,它是一颗多路平衡查找树. ...

  6. 再也不怕面试官问我平时都从什么途径学习了

    上次面试,面试官问我平时都从哪些途径学习,我反手就给他看了经常学习的数百个公众号,下面这些更是优中选优的技术号 马哥Linux运维 开发者技术前线:马哥教育是国内顶级的 Linux 云计算.Pytho ...

  7. 前端集合删除对象_【两万字】面试官:听说你精通集合源码,接我二十个问题!...

    问题一:看到这个图,你会想到什么? (PS:截图自<编程思想>) 答: 这个图由Map指向Collection的Produces并不是说Map是Collection的一个子类(子接口),这 ...

  8. 下血本买的!Flutter中网络图片加载和缓存源码分析,看看这篇文章吧!

    目录 想要成为一名优秀的Android开发,你需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样. PagerAdapter 介绍 ViwePager 缓存策略 ViewPager 布局处 ...

  9. 面试官问:平时碰到系统CPU飙高和频繁GC,你会怎么排查?

    点击上方"方志朋",选择"设为星标" 做积极的人,而不是积极废人 处理过线上问题的同学基本上都会遇到系统突然运行缓慢,CPU 100%,以及Full GC次数过 ...

最新文章

  1. HDU1598最小生成树+贪心处理
  2. Ch -- 一个 C/C++ 解释器
  3. 走,去谷歌的机房逛逛
  4. sublime交互执行python文件方法
  5. HTML系列(七):多媒体
  6. 计算机网络总结各种协议首部的长度,计算机网络协议总结
  7. NEO技术文章征集大赛
  8. XP系统每次打开我的电脑出现自动扫描现象解决办法
  9. C++编程中const和#define的区别
  10. C中无警告输出size_t的值
  11. centos修改jdk之后无法生效问题
  12. iotop监视磁盘I/O
  13. 90后的青春,定格在被淡忘的QQ空间里
  14. php高清晰度无损压缩
  15. 开发撞墙之奇怪的需求:经纬度带符号转换
  16. 家用无线路由器哪个品牌好?程序员分享值得推荐的无线路由器
  17. PhotoSweeper X for Mac(重复照片清理工具)
  18. ios设备如何安装chatgpt
  19. win7计算机管理找不到文件夹,Win7文件夹选项不见了如何解决?
  20. Linux基础 - 基本命令

热门文章

  1. matlab 同一坐标系 散点图 t,matlab上机练习
  2. mysql 安装1607_mysql服务启动报1607error
  3. 《计算机应用基础》模拟试卷三,2015年《计算机应用基础》模拟试题及答案(一)...
  4. python如何画虚线_Python威力巨大,五分钟如何绘制出漂亮的系统架构图?
  5. 罗技 连点 脚本_罗技推出多款《英雄联盟》联名外设 看了就忍不住想要
  6. shell脚本拼接中间带空格的两个变量成一个变量
  7. 操作系统之文件管理:7、文件共享与文件保护(软连接、硬链接、口令保护、加密保护、访问控制)
  8. 09. 用两个栈实现队列
  9. Python 输出HTML实体字符(#x***转html,html符号乱码,中文乱码)
  10. kali安装pip3