本文收录于《技术专家修炼》

文中配套资料合集
路线导图高清源文件

点击跳转到文末点击底部卡片回复「资料」领取

哈喽,大家好,我是一条~

最近粉丝问我有没有Java自学路线,有了方向才能按图索骥,事半功倍。

我一想确实如此,自己去年总结了一份,但有些地方需要修改,索性利用国庆假期,重新整理一份。

没错,就是这篇文章,本文分为学习路线配套资料两部分。

文章目录

  • 自学路线
    • 0.路线导学
      • 按计划行事
      • 抱团生长
      • 贵在坚持
    • 1.Java基础
      • 基本数据类型
      • 引用数据类型
      • 访问修饰符
      • static关键字
      • final关键字
      • 面向对象三大特性
      • 完整讲解
      • 入门练习案例
    • 2.JavaWeb
      • HTTP网络请求方式
      • GET和POST
      • 幂等性
      • 如何保证幂等性
      • 常见的网络状态码
      • 转发和重定向
      • Servlet
      • Servlet的生命周期
      • session、cookie、token
      • MVC与三层架构
      • 完整讲解
    • 3.集合
      • ArrayList
      • LinkedList
      • 和ArrayList对比一下
      • 完整讲解
    • 4.JVM
      • JVM的体系架构
      • 类装载器classloader
      • 垃圾回收
      • 完整讲解
    • 5.多线程
      • 并行和并发
      • 线程和进程
      • 守护线程
      • 创建线程4种方式
      • synchronized 底层实现
      • synchronized 和 volatile 的区别
      • synchronized 和 Lock 区别
      • synchronized 和 ReentrantLock 区别
    • 6.设计模式
      • 原型模式
      • 更多设计模式
    • 7.SSM框架
      • ORM 框架?
      • MyBatis 中 #{}和 的区别
      • 什么是Spring
      • 什么是 aop
      • 什么是 ioc
      • spring mvc 运行流程
      • 什么是 spring boot?
      • 为什么要用 spring boot?
    • 8.Redis
      • 什么是Redis
      • Redis 的功能
      • Redis 和 memcache
      • Redis 为什么是单线程的
      • 缓存穿透
      • Redis 数据类型
    • 9.Zookeeper
      • 什么是zookeeper
      • zookeeper 的功能
      • zookeeper 的部署模式
    • 10.Kafka
      • kafka和zookeeper的关系
      • kafka的数据保留的策略?
      • kafka性能瓶颈
      • kafka集群
    • 11.ES
    • 12.Dubbo
    • 13.SpringCloud
      • 什么是 spring cloud
      • spring cloud 的核心组件
      • 断路器的作用
    • 14.Nginx
    • 15.Netty
    • 16.架构设计
      • V0——单体架构
      • V1——负载均衡
      • V2——服务限流
      • V3 同步状态
      • V4线程优化
      • V5业务逻辑
      • V6流量削峰
    • 17.Linux
    • 18.Git
      • 命令大全
    • 19.数据结构和算法
      • 排序算法
      • 1eetcode刷题
    • 20.计算机网络
    • 21.操作系统
    • 22.计算机组成原理
    • 23.程序员英语
    • 24.写作能力
    • 25.演讲能力
    • 26.管理能力
  • 配套资料
    • 电子书
      • 1.Java核心技术卷一、二
      • 2.深入理解Java虚拟机
      • 3.java并发编程的艺术
      • 4.阿里巴巴Java开发手册
      • Redis 深度历险:核心原理与应用实践
      • Apache Kafka实战
    • 视频
      • 1.零基础入门
      • 2.JVM
      • 3.SpringCloud
      • 4.程序员一条
    • 面试集
      • 一条整理
      • 204道全路线面试题合集
      • Java核心知识点
  • 粉丝福利

自学路线

0.路线导学

Java学习,如逆水行舟,不进则退。而自学,逆水还得加个水逆,难上加难。

所以我们要做好打持久战的准备。

按计划行事

凡事预则立,不预则废。一个好的计划是成功的一半,而这一半,一条已经帮你整理好了,你只需要收藏即可。

该路线图左侧为主路线,需循序渐进,步步为营;右侧为辅助路线,需贯穿始终,熟练掌握。

建议做好时间规划,不断的提高自己的学习效率,学习过程中尽量把手机调至静音给自己一个安静的学习环境和氛围。

抱团生长

独脚难行,孤掌难鸣,一个人的力量终究是有限的,一个人的旅途也注定是孤独的。当你定好计划,怀着满腔热血准备出发的时候,一定要找个伙伴,和唐僧西天取经一样,师徒四人团结一心才能通过九九八十一难。

在学习过程中看下自己身边有没有Java这方面的大神,尽量多问,多交流,如果没有的话,来找我,我一定知无不言言无不尽,还可以给你找一群志同道合的人。水涨船高,柴多火旺,就是这个道理,闭门造车注定会半途而废。

贵在坚持

驽马十驾,功在不舍。自学Java非一日之功,你知道的越多,不知道的也越多。所以,为自己找一个动力,为了改变命运,或是为了心爱的人,或是为了让别人高看一眼。男儿何不带吴钩,收取关山十五州。岁月无情,余生有涯,请将生活扛在肩上,只顾风雨兼程。

1.Java基础

学习任何语言,都是先从他的基本语法开始,如果你有C语言的基础,会容易许多,没有也不用现学。

基本数据类型

Java 语言提供了 8 种基本类型,大致分为 4 类(8位=1字节)

  • 整数型

    • byte - 1字节
    • short - 2字节
    • int - 4字节
    • long - 8字节,赋值时一般在数字后加上 lL
  • 浮点型
    • float - 4字节,直接赋值时必须在数字后加上 fF
    • double - 8字节,赋值时一般在数字后加 dD
  • 字符型
    • char - 2字节,存储 Unicode 码,用单引号赋值
  • 布尔型
    • boolean - 1字节,只有 true 和 false 两个取值,一个字节就够了

引用数据类型

简单来说,所有的非基本数据类型都是引用数据类型,除了基本数据类型对应的引用类型外,类、 接口类型、 数组类型、 枚举类型、 注解类型、 字符串型都属于引用类型。

主要有以下区别:

1、存储位置

  • 基本变量类型在方法中定义的非全局基本数据类型变量的具体内容是存储在栈中的
  • 引用数据类型变量其具体内容都是存放在堆中的,而栈中存放的是其具体内容所在内存的地址

2、传递方式

  • 基本数据类型是按值传递
  • 引用数据类型是按引用传递

访问修饰符

访问修饰符就是限制变量的访问权限的。

比如你有个“赚钱”的方法,谁都不想给用,那就把方法设成private(私有);

后来你有了老婆孩子,你想让他们也会赚钱,就得设置成default(同一个包);

后来你又有了第二个孩子,但你发现他不会赚钱的方法,为啥呢?因为你被绿了(default不支持不同包的子类);

可为了大局,你还是选择接受这个孩子,悄悄把方法设置成了proteced(保护子类,即使不同包);

后来你老了,明白了开源才是共赢,就设置成了public(公有的);

不知道你听懂了吗,估计看到被那啥了就不想看了吧,没关系,看图(也是绿的)

static关键字

主要意义:

我日常调用方法都是对象.方法,static的主要意义就是可以创建独立于具体对象的域变量或者方法。也就是实现即使没有创建对象,也能使用属性和调用方法!

另一个比较关键的作用就是 用来形成静态代码块以优化程序性能static块可以置于类中的任何地方,可以有多个。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次,可以用来优化程序性能

通俗理解:

static是一个可以让你升级的关键字,被static修饰,你就不再是你了。

final关键字

final翻译成中文是“不可更改的,最终的”,顾名思义,他的功能就是不能再修改,不能再继承。我们常见的String类就是被final修饰的。

将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。

按照Java代码惯例,final变量就是常量,而且通常常量名要大写:

  • final关键字可以用于成员变量、本地变量、方法以及类。
  • final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。
  • 不能够对final变量再次赋值。
  • final方法不能被重写。
  • final类不能被继承。
  • 接口中声明的所有变量本身是final的。
  • final和abstract这两个关键字是反相关的,final类就不可能是abstract的。

面向对象三大特性

封装

1.什么是封装

封装又叫隐藏实现。就是只公开代码单元的对外接口,而隐藏其具体实现。

其实生活中处处都是封装,手机,电脑,电视这些都是封装。你只需要知道如何去操作他们,并不需要知道他们里面是怎么构造的,怎么实现这个功能的。

2.如何实现封装

在程序设计里,封装往往是通过访问控制实现的。也就是刚才提到的访问修饰符。

3.封装的意义

封装提高了代码的安全性,使代码的修改变的更加容易,代码以一个个独立的单元存在,高内聚,低耦合。

好比只要你手机的充电接口不变,无论以后手机怎么更新,你依然可以用同样的数据线充电或者与其他设备连接。

封装的设计使使整个软件开发复杂度大大降低。我只需要使用别人的类,而不必关心其内部逻辑是如何实现的。我能很容易学会使用别人写好的代码,这就让软件协同开发的难度大大降低。

封装还避免了命名冲突的问题。

好比你家里有各种各样的遥控器,但比还是直到哪个是电视的,哪个是空调的。因为一个属于电视类一个属于空调类。不同的类中可以有相同名称的方法和属性,但不会混淆。

继承

继承的主要思想就是将子类的对象作为父类的对象来使用。比如王者荣耀的英雄作为父类,后裔作为子类。后裔有所有英雄共有的属性,同时也有自己独特的技能。

多态

多态的定义:

指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)

简单来说,同样调用攻击这个方法,后裔的普攻和亚瑟的普攻是不一样的。

多态的条件:

  • 要有继承
  • 要有重写
  • 父类引用指向子类对象

多态的好处:

多态对已存在代码具有可替换性。

多态对代码具有可扩充性。

它在应用中体现了灵活多样的操作,提高了使用效率。

多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。

Java中多态的实现方式:

  • 接口实现
  • 继承父类进行方法重写
  • 同一个类中进行方法重载

完整讲解

Java基础完整讲解

入门练习案例

《入门练习100例》

2.JavaWeb

JavaWeb是用Java技术来解决相关web互联网领域的技术栈。Web就是网页,分为静态和动态。涉及 的知识点主要包括jsp,servlet,tomcat,http,MVC等知识。

本章难度不高,但也不可忽视。其中前端基础不需花过多时间,重点放在Tomcat上,会陪伴你整个Java生涯。

HTTP网络请求方式

  • GET:最常用的方式,用来向服务器请求数据,没有请求体,请求参数放在URL后面。
  • POST:用于向表单提交数据,传送的数据放在请求体中。
  • PUT:用来向服务器上传文件,一般对应修改操作,POST用于向服务器发送数据,PUT用于向服务器储存数据。没有验证机制,任何人都可以操作,存在安全问题。具有幂等性。
  • DELETE:用于删除服务器上的文件,具有幂等性。同样存在安全问题。
  • HEAD:用HEAD进行请求服务器时,服务器只返回响应头,不返回响应体。与GET一样没有请求体,常用于检查请求的URL是否有效。
  • PATCH:对资源进行部分修改。与PUT区别在于,PUT是修改所有资源,替代它,而PATCH只是修改部分资源。
  • TRACE:用来查看一个请求,经过网关,代理到达服务器,最后请求的变换。因安全问题被禁用。
  • OPTIONS:当客户端不清楚对资源操作的方法,可以使用这个,具有幂等性。

GET和POST

  1. 作用不同:GET 用于获取资源,而 POST 用于传输实体主体。
  2. 参数位置不一样: GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在实体主体中。虽然GET的参数暴露在外面,但可以通过加密的方式处理,而 POST 参数即使存储在实体主体中,我们也可以通过一些抓包工具如(Fiddler)查看。
  3. 幂等性:GET是幂等性,而POST不是幂等性。(面试官紧接着可能就会问你什么是幂等性?如何保证幂等性?)
  4. 安全性:安全的 HTTP 方法不会改变服务器状态,也就是说它只是可读的。 GET 方法是安全的,而 POST 却不是,因为 POST 的目的是传送实体主体内容,这个内容可能是用户上传的表单数据,上传成功之后,服务器可能把这个数据存储到数据库中,因此状态也就发生了改变。

幂等性

是否具有幂等性也是一个http请求的重要关注点。

幂等性:指的是同样的请求不管执行多少次,效果都是一样,服务器状态也是一样的。具有幂等性的请求方法没有副作用。(统计用途除外)

如何保证幂等性

假设这样一个场景:有时我们在填写某些form表单时,保存按钮不小心快速点了两次,表中竟然产生了两条重复的数据,只是id不一样。

这是一个比较常见的幂等性问题,在高并发场景下会变得更加复杂,那怎么保证接口的幂等性呢?

1.insert前select

插入数据前先根据某一字段查询一下数据库,如果已经存在就修改,不存在再插入。

2.加锁

加锁可解决一切问题,但也要考虑并发性。

主要包括悲观锁,乐观锁,分布式锁。

悲观锁的并发性较低,更适合使用在防止数据重复的场景,注意幂等性不光是防止重复还需要结果相同。

乐观锁可以很高的提升性能,也就是常说的版本号。

分布式锁应用在高并发场景,主要用redis来实现。

3.唯一索引

通过数据库的唯一索引来保证结果的一致性和数据的不重复。

4.Token

两次请求,第一请求拿到token,第二次带着token去完成业务请求。

常见的网络状态码

网络状态码共三位数字组成,根据第一个数字可分为以下几个系列:

1xx(信息性状态码)

代表请求已被接受,需要继续处理。

包括:100、101、102

这一系列的在实际开发中基本不会遇到,可以略过。

2xx(成功状态码)

表示成功处理了请求的状态代码。

200:请求成功,表明服务器成功了处理请求。

202:服务器已接受请求,但尚未处理。

204:服务器成功处理了请求,但没有返回任何内容。

206:服务器成功处理了部分 GET 请求。

3xx(重定向状态码)

300:针对请求,服务器可执行多种操作。

301:永久重定向

302:临时性重定向

303:303与302状态码有着相同的功能,但303状态码明确表示客户端应当采用GET方法获取资源。

301和302的区别?

301比较常用的场景是使用域名跳转。比如,我们访问 http://www.baidu.com 会跳转到https://www.baidu.com,发送请求之后,就会返回301状态码,然后返回一个location,提示新的地址,浏览器就会拿着这个新的地址去访问。

302用来做临时跳转比如未登陆的用户访问用户中心重定向到登录页面。

4xx(客户端错误状态码)

400:该状态码表示请求报文中存在语法错误。但浏览器会像200 OK一样对待该状态码。

401:表示发送的请求需要有通过HTTP认证的认证信息。比如token失效就会出现这个问题。

403:被拒绝,表明对请求资源的访问被服务器拒绝了。

404:找不到,表明服务器上无法找到请求的资源,也可能是拒绝请求但不想说明理由。

5xx(服务器错误状态码)

500:服务器本身发生错误,可能是Web应用存在的bug或某些临时的故障。

502:该状态码表明服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。

⚠️有时候返回的状态码响应是错误的,比如Web应用程序内部发生错误,状态码依然返回200

转发和重定向

上面提到了重定向,那你知道什么是转发吗?

1.转发

A找B借钱,B没有钱,B去问C,C有钱,C把钱借给A的过程。

客户浏览器发送http请求,web服务器接受此请求,调用内部的一个方法在容器内部完成请求处理和转发动作,将目标资源发送给客户。

整个转发一个请求,一个响应,地址栏不会发生变化,不能跨域访问。

2.重定向

A找B借钱,B没有钱,B让A去找C,A又和C借钱,C有钱,C把钱借给A的过程。

客户浏览器发送http请求,web服务器接受后发送302状态码响应及对应新的location给客户浏览器,客户浏览器发现是302响应,则自动再发送一个新的http请求,请求url是新的location地址,服务器根据此请求寻找资源并发送给客户。

两个请求,两个响应,可以跨域。

Servlet

servlet是一个比较抽奖的概念,也是web部分的核心组件,大家回答这个问题一定要加入自己的理解,不要背定义。

servlet其实就是一个java程序,他主要是用来解决动态页面的问题。

之前都是浏览器像服务器请求资源,服务器(tomcat)返回页面,但用户多了之后,每个用户希望带到不用的资源。这时就该servlet上场表演了。

servlet存在于tomcat之中,用来网络请求与响应,但他的重心更在于业务处理,我们访问京东和淘宝的返回的商品是不一样的,就需要程序员去编写,目前MVC三层架构,我们都是在service层处理业务,但这其实是从servlet中抽取出来的。

看一下servlet处理请求的过程:

Servlet的生命周期

Servlet生命周期分为三个阶段:

  • 初始化阶段 调用init()方法
  • 响应客户请求阶段  调用service()方法-àdoGet/doPost()
  • 终止阶段  调用destroy()方法

session、cookie、token

首先我们要明白HTTP是一种无状态协议,怎么理解呢?很简单

夏洛:大爷,楼上322住的是马冬梅家吧?
大爷:马冬什么?
夏洛:马冬梅。
大爷:什么冬梅啊?
夏洛:马冬梅啊。
大爷:马什么梅?
夏洛:行,大爷你先凉快着吧。

这段对话都熟悉吧,HTTP就是那个大爷,那如果我们就直接把“大爷”放给用户,用户不用干别的了,就不停的登录就行了。

既然“大爷不靠谱”,我们找“大娘”去吧。

哈哈哈,开个玩笑,言归正传。

为了解决用户频繁登录的问题,在服务端和客户端共同维护一个状态——会话,就是所谓session,我们根据会话id判断是否是同一用户,这样用户就开心了。

但是服务器可不开心了,因为用户越来越多,都要把session存在服务器,这对服务器来说是一个巨大的开销,这是服务器就找来了自己的兄弟帮他分担(集群部署,负载均衡)。

但是问题依然存在,如果兄弟挂了怎么办,兄弟们之间的数据怎么同步,用户1把session存放在机器A上,下次访问时负载均衡到了机器B,完了,找不到,用户又要骂娘。

这时有人思考,为什么一定要服务端保存呢,让客户端自己保存不就好了,所以就诞生了cookie,下一次请求时客户段把cookie发送给服务器,说我已经登录了。

但是空口无凭,服务器怎么知道哪个cookie是我发过去的呢?如何验证成了新的问题。

有人想到了一个办法,用加密令牌,也就是token,服务器发给客户端一个令牌,令牌保存加密后id和密钥,下一次请求时通过headers传给服务端,由于密钥别人不知道,只有服务端知道,就实现了验证,且别人无法伪造。

MVC与三层架构

三层架构与MVC的目标一致:都是为了解耦和、提高代码复用。MVC是一种设计模式,而三层架构是一种软件架构。

MVC

Model 模型

模型负责各个功能的实现(如登录、增加、删除功能),用JavaBean实现。

View 视图

用户看到的页面和与用户的交互。包含各种表单。 实现视图用到的技术有html/css/jsp/js等前端技术。

常用的web 容器和开发工具

Controller 控制器

控制器负责将视图与模型一一对应起来。相当于一个模型分发器。接收请求,并将该请求跳转(转发,重定向)到模型进行处理。模型处理完毕后,再通过控制器,返回给视图中的请求处。

三层架构

表现层(UI)(web层)、业务逻辑层(BLL)(service层)、数据访问层(DAL)(dao层) ,再加上实体类库(Model)

  • 实体类库(Model),在Java中,往往将其称为Entity实体类。数据库中用于存放数据,而我们通常选择会用一个专门的类来抽象出数据表的结构,类的属性就一对一的对应这表的属性。一般来说,Model实体类库层需要被DAL层,BIL层和UI层引用。
  • 数据访问层(DAL),主要是存放对数据类的访问,即对数据库的添加、删除、修改、更新等基本操作,DAL就是根据业务需求,构造SQL语句,构造参数,调用帮助类,获取结果,DAL层被BIL层调用
  • 业务逻辑层(BLL),BLL层好比是桥梁,将UI表示层与DAL数据访问层之间联系起来。所要负责的,就是处理涉及业务逻辑相关的问题,比如在调用访问数据库之前,先处理数据、判断数据。

完整讲解

JavaWeb完整讲解

3.集合

工欲善其事必先利其器,集合就是我们的器。

ArrayList

底层实现

由什么组成,我说了不算,看源码。怎么看呢?

List<Object> list = new ArrayList<>();

新建一个ArrayList,按住ctrlcommand用鼠标点击。

    /*** The array buffer into which the elements of the ArrayList are stored.* The capacity of the ArrayList is the length of this array buffer. Any* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA* will be expanded to DEFAULT_CAPACITY when the first element is added.* 翻译* 数组缓冲区,ArrayList的元素被存储在其中。ArrayList的容量是这个数组缓冲区的长度。* 任何空的ArrayList,如果elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,* 当第一个元素被添加时,将被扩展到DEFAULT_CAPACITY。*/transient Object[] elementData;

毋庸置疑,底层由数组组成,那数组的特点就是ArrayList的特点。

  • 由于数组以一块连续的内存空间,每一个元素都有对应的下标,查询时间复杂度为O(1)。好比你去住酒店,每个房间都挨着,房门都写着房间号。你想找哪一间房是不是很容易。
  • 相对的,一块连续的内存空间你想打破他就没那么容易,牵一发而动全身,所以新增和删除的时间复杂度为O(n),想像你在做excel表格的时候,想增加一列,后面的列是不是都要跟着移动。
  • 元素有序,可重复。可用在大多数的场景,这个就不需要过多解释了。

扩容

我们知道数组是容量不可变的数据结构,随着元素不断增加,必然要扩容。

所以扩容机制也是集合中非常容易爱问的问题,在源码中都可以一探究竟。

1.初始化容量为10,也可以指定容量创建。

    /*** Default initial capacity.* 定义初始化容量*/private static final int DEFAULT_CAPACITY = 10;

2.数组进行扩容时,是将旧数据拷贝到新的数组中,新数组容量是原容量的1.5倍。(这里用位运算是为了提高运算速度)

private void grow(int minCapacity) {int newCapacity = oldCapacity + (oldCapacity >> 1);
}

3.扩容代价是很高得,因此再实际使用时,我们因该避免数组容量得扩张。尽可能避免数据容量得扩张。尽可能,就至指定容量,避免数组扩容的发生。

为什么扩容是1.5倍?

  • 如果大于1.5,也就是每次扩容很多倍,但其实我就差一个元素的空间,造成了空间浪费。
  • 如果小于1.5,扩容的意义就不大了,就会带来频繁扩容的问题。

所以,1.5是均衡了空间占用和扩容次数考虑的。

线程安全问题

怎么看线程安全?说实话我以前都不知道,看网上说安全就安全,说不安全就不安全。

其实都在源码里。找到增加元素的方法,看看有没有加锁就知道了。

    public void add(int index, E element) {rangeCheckForAdd(index);ensureCapacityInternal(size + 1);  // Increments modCount!!System.arraycopy(elementData, index, elementData, index + 1,size - index);elementData[index] = element;size++;}

没有加锁,所以线程不安全

在多线程的情况下,插入数据的时可能会造成数据丢失,一个线程在遍历,另一个线程修改,会报ConcurrentModificationException(并发修改异常)错误.

多线程下使用怎么保证线程安全?

保证线程安全的思路很简单就是加锁,但是你可没办法修改源码去加个锁,但是你想想编写java的大佬会想不到线程安全问题?

早就给你准备了线程安全的类。

1.Vector

Vector是一个线程安全的List类,通过对所有操作都加上synchronized关键字实现。

找到add方法,可以看到被synchronized关键字修饰,也就是加锁,但synchronized是重度锁,并发性太低,所以实际一般不使用,随着java版本的更新,慢慢废弃。

public void add(E e) {int i = cursor;synchronized (Vector.this) {checkForComodification();Vector.this.add(i, e);expectedModCount = modCount;}cursor = i + 1;lastRet = -1;}

2.Collections

注意是Collections而不是Collection

Collections位于java.util包下,是集合类的工具类,提供了很多操作集合类的方法。其中Collections.synchronizedList(list)可以提供一个线程安全的List

对于Map、Set也有对应的方法

3.CopyOnWrite(写时复制)

写时复制,简称COW,是计算机程序设计领域中的一种通用优化策略。

当有多人同时访问同一资源时,他们会共同获取指向相同的资源的指针,供访问者进行读操作。

当某个调用者修改资源内容时,系统会真正复制一份副本给该调用者,而其他调用者所见到的最初的资源仍然保持不变。修改完成后,再把新的数据写回去。

通俗易懂的讲,假设现在有一份班级名单,但有几个同学还没有填好,这时老师把文件通过微信发送过去让同学们填写(复制一份),但不需要修改的同学此时查看的还是旧的名单,直到有同学修改好发给老师,老师用新的名单替换旧的名单,全班同学才能查看新的名单。

共享读,分开写。读写分离,写时复制。

在java中,通过CopyOnWriteArrayListCopyOnWriteArraySet容器实现了 COW 思想。

平时查询的时候,都不需要加锁,随便访问,只有在更新的时候,才会从原来的数据复制一个副本出来,然后修改这个副本,最后把原数据替换成当前的副本。修改操作的同时,读操作不会被阻塞,而是继续读取旧的数据。

    /** The lock protecting all mutators */final transient ReentrantLock lock = new ReentrantLock();/** The array, accessed only via getArray/setArray. */private transient volatile Object[] array;

源码里用到了ReentrantLock锁和volatile关键字,会在《资深程序员修炼》专栏中做全面深度讲解。

LinkedList

LinkedListArrayList同属于List集合。其共同特点可归纳为:

存储单列数据的集合,存储的数据是有序并且是可以重复的。

但两者也有不同,往下看吧

底层实现

LinkedList类的底层实现的数据结构是一个双向链表。同时还实现了Deque接口,所以会有些队列的特性,会在下面讲。

class LinkedList<E>extends AbstractSequentialList<E>implements List<E>, Deque<E>, Cloneable, java.io.Serializable

先简单说一下链表这种数据结构,与数组相反,链表是一种物理存储单元上非连续、非顺序的存储结构,一个最简单的链表(单链表)有节点Node和数值value组成。通俗的讲,就像串在一起的小鱼干,中间用线连着。

transient Node<E> first;transient Node<E> last;

链表中保存着对最后一个节点的引用,这就是双端链表

在单链表的结点中增加一个指向其前驱的pre指针就是双向链表,一种牺牲空间换时间的做法。

双端链表不同于双向链表,切记!

关于链表更详细代码级讲解会放《糊涂算法》专栏更新。敬请期待!

简单了解过后分析一下链表的特点:

  • 查询速度慢,因为是非连续空间,没有下标。想像你需要在一份名单上找到你的名字,没有序号,你只能从头开始一个一个的看。
  • 删改速度快,因为非连续,也就没有那么多约束。想像从一根项链上扣下来一块,只需要改变引用就可以了,不会牵一发而动全身。
  • 元素有序,可重复。

如何解决查询慢的问题?

如果我查找的元素在尾部,则需要遍历整个链表,所以有了双端链表。

即使不在尾部,我如果只能一个方向遍历,也很麻烦,所以有了双向队列,牺牲空间换时间。

那么空间可不可以再牺牲一点?

可以,就是跳跃链表,简称「跳表」。

通过建立多级索引来加快查询速度。

线程安全问题

老办法,看看add()方法。分为「头插法」和「尾插法」。

    /*** Inserts the specified element at the beginning of this list.** @param e the element to add*/public void addFirst(E e) {linkFirst(e);}/*** Appends the specified element to the end of this list.** <p>This method is equivalent to {@link #add}.** @param e the element to add*/public void addLast(E e) {linkLast(e);}

都没加锁,百分之一百的不安全。

如何解决线程不安全问题

1.ConcurrentLinkedQueue

一个新的类,位于java.util.concurrent(juc)包下。实现了Queue接口。

class ConcurrentLinkedQueue<E> extends AbstractQueue<E>implements Queue<E>, java.io.Serializable{}

使用violate关键字实现加锁。

 private transient volatile Node<E> head;private transient volatile Node<E> tail;

1.Collections

ArrayList一样,使用Collections.synchronizedList()

Map:存储双列数据的集合,通过键值对存储数据,存储 的数据是无序的,Key值不能重复,value值可以重复

和ArrayList对比一下

共同点:有序,可重复。线程不安全。

不同点:底层架构,查询和删改的速度

完整讲解

集合完整讲解

4.JVM

重点来了,Java程序员一定要深入研究的内容

JVM的体系架构

JVM体系结构如下图所示,将按照从上到下的顺序讲解

类装载器classloader

负责将class文件的字节码内容加载到内存中并将这些内容转换成方法区中的运行时数据结构,class文件在文件开头有特定标识(cafe babe:Java图标——咖啡和橡树)。

通俗来讲:classloader相当于快递员的作用,只负责加载,至于是否能运行,由Execution Engine决定

双亲委派机制

当一个类收到类加载请求,他会先把这个请求交给他的父类,只有父类无法完成这个请求时,子加载器才会尝试自己去加载。

双亲委派的好处是保护Java核心类,比如加载位于rt.jar中的java.lang.Object,不管是哪个加载器加载的,最终都会交给启动类加载器,这样就保证了不同的类加载器得到的都是同一个Object对象。

代码举例:查看类是被那个加载器加载的

/*** @Author: 一条IT* @Date: 2020/12/3 21:28*/
public class Test {public static void main(String[] args) {System.out.println(Object.class.getClassLoader());System.out.println(Test.class.getClassLoader().getParent().getParent());System.out.println(Test.class.getClassLoader().getParent());System.out.println(Test.class.getClassLoader());}
}

输出

null
null
sun.misc.Launcher$ExtClassLoader@1b6d3586
sun.misc.Launcher$AppClassLoader@14dad5dc

因为Object是jdk自带的,所以在加载的时候是走Bootstrap启动类加载器,而Bootstrap加载器是C++语言写的,所以在查的时候是null,报了NullPointException();Test类自己写的,走AppClassLoder,他的父类是扩展加载器,再父类是启动类加载器,也输出Null

沙箱安全机制

主要是防止恶意代码污染java源代码,比如定义了一个类名为String所在包为java.lang,因为这个类本来是属于jdk的,如果没有沙箱安全机制的话,这个类将会污染到我所有的String,但是由于沙箱安全机制,所以就委托顶层的bootstrap加载器查找这个类,如果没有的话就委托extsion,extsion没有就到appclassloader,但是由于String就是jdk的源代码,所以在bootstrap那里就加载到了,先找到先使用,所以就使用bootstrap里面的String,后面的一概不能使用,这就保证了不被恶意代码污染。

垃圾回收

垃圾回收是重点难,先理解了垃圾回收,才能理解调优的思路。

判断垃圾

判断是否是垃圾共有两种方法。引用计数法和可达性分析

1.引用计数法

非常好理解,引用一次标记一次,没有被标记的就是垃圾。

在堆中存储对象时,在对象头处维护一个counter计数器,如果一个对象增加了一个引用与之相连,则将counter++

如果一个引用关系失效则counter--。如果一个对象的counter变为0,则说明该对象已经被废弃,不处于存活状态,此时可以被回收。

2.引用计数的缺点

  • 效率低
  • 无法分析循环引用问题

3.可达性分析

类似的树结构,从根结点出发,即GC root,把有关系的对象用一颗树链接起来

那么我们遍历这棵树,没遍历到的对象,就是垃圾

4.有哪些可以做GC Roots的对象?

  • 虚拟机栈(栈桢中的本地变量表)中的引用的对象
  • 方法区中的类静态属性引用的对象
  • 方法区中的常量引用的对象
  • 本地方法栈中JNI(Native方法)的引用的对象

回收算法

回收算法是垃圾回收的思想,回收器是垃圾回收的实现

1.标记-清除

两次遍历:

  • 标记垃圾
  • 清除垃圾

优点:

  • 不需要格外空间,适合回收对象较少的区域

缺点:

  • 效率低,遍历两次,时间复杂度O(n^2)
  • 会有线程停顿,stop the world (STW)
  • 空间碎片,因为垃圾可能不是连续的,大量的空间碎片会导致提前GC,这也是最主要的问题。

2.标记-复制

将空间分为相等大小的两部分,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。牺牲空间解决碎片问题。

优点:

  • 高效无碎片

缺点:

  • 占用大量空间

3.标记-整理

同样是为了解决空间碎片提出,区别是通过牺牲时间的方式。

和标记-清除类似,不一样的是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。

优点:

  • 解决空间碎片的问题
  • 不浪费空间

缺点:

  • 相对比较耗时

完整讲解

JVM完整讲解

5.多线程

理解多线程,才能更好的理解框架源码,进行高并发的架构设计,重中之重。

并行和并发

并行:多个任务在同一个 CPU 核上,按细分的时间片轮流(交替)执行,从逻辑上来看那些任务是同时执行。

并发:多个处理器或多核处理器同时处理多个任务。

举例:

并发 = 两个队列和一台咖啡机。

并行 = 两个队列和两台咖啡机。

线程和进程

一个程序下至少有一个进程,一个进程下至少有一个线程,一个进程下也可以有多个线程来增加程序的执行速度。

守护线程

守护线程是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。在 Java 中垃圾回收线程就是特殊的守护线程。

创建线程4种方式

  • 继承 Thread 重新 run 方法;

  • 实现 Runnable 接口;

  • 实现 Callable 接口。

  • 线程池

synchronized 底层实现

synchronized 是由一对 monitorenter/monitorexit 指令实现的,monitor 对象是同步的基本实现单元。

在 Java 6 之前,monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作,性能也很低。

但在 Java 6 的时候,Java 虚拟机 对此进行了大刀阔斧地改进,提供了三种不同的 monitor 实现,也就是常说的三种不同的锁:偏向锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。

synchronized 和 volatile 的区别

volatile 是变量修饰符;synchronized 是修饰类、方法、代码段。

volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。

volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。

synchronized 和 Lock 区别

synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。

synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁。

lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。

通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

synchronized 和 ReentrantLock 区别

synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但是在 Java 6 中对 synchronized 进行了非常多的改进。

主要区别如下:

ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;

ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;

ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰方法、代码块等。

volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化。

6.设计模式

好多人觉得设计模式模式,那是因为你学的还不够深入,还没有看过源码,所以我特意将设计模式往前放了。

原型模式

定义

官方定义

用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。

通俗解读

在需要创建重复的对象,为了保证性能,本体给外部提供一个克隆体进行使用

类似我国的印刷术,省去new的过程,通过copy的方式创建对象。

结构图

代码实现

目录结构

建议跟着一条学设计模式的小伙伴都建一个maven工程,并安装lombok依赖和插件。

并建立如下包目录,便于归纳整理。

pom如下

    <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.10</version></dependency>

开发场景

假设一条开发了一个替代Mybatis的框架,叫YitiaoBatis,每次操作数据库,从数据库里面查出很多记录,但是改变的部分是很少的,如果每次查数据库,查到以后把所有数据都封装一个对象,就会导致要new很多重复的对象,造成资源的浪费。

一条想到一个解决办法,就是把查过的数据保存起来,下来查相同的数据,直接把保存好的对象返回,也就是缓存的思想。

我们用代码模拟一下:

1.创建Yitiao实体类

/*** author:一条*/
@Data
@AllArgsConstructor
public class Yitiao {private String name;private Integer id;private String wechat;public Yitiao(){System.out.println("Yitiao对象创建");}
}

2.创建YitiaoBatis

/*** author:一条*/
public class YitiaoBatis {//缓存Map    private Map<String,Yitiao> yitiaoCache = new HashMap<>();//从缓存拿对象public Yitiao getYitiao(String name){//判断缓存中是否存在if (yitiaoCache.containsKey(name)){Yitiao yitiao = yitiaoCache.get(name);System.out.println("从缓存查到数据:"+yitiao);return yitiao;}else {//模拟从数据库查数据Yitiao yitiao = new Yitiao();yitiao.setName(name);yitiao.setId(1);yitiao.setWechat("公众号:一条coding");System.out.println("从数据库查到数据:"+yitiao);//放入缓存yitiaoCache.put(name,yitiao);return yitiao;}}
}

3.编写测试类

/*** author:一条*/
public class MainTest {public static void main(String[] args) {YitiaoBatis yitiaoBatis = new YitiaoBatis();Yitiao yitiao1 = yitiaoBatis.getYitiao("yitiao");System.out.println("第一次查询:"+yitiao1);Yitiao yitiao2 = yitiaoBatis.getYitiao("yitiao");System.out.println("第二次查询:"+yitiao2);}
}

输出结果

从结果可以看出:

  • 对象创建了一次,有点单例的感觉
  • 第一次从数据库查,第二次从缓存查

好像是实现了YitiaoBatis框架的需求,思考

五万字15张导图Java自学路线,小白零基础入门,程序员进阶,收藏这篇就够了相关推荐

  1. 机械转行java自学经历,零基础学java,血泪总结的干货

    机械转行java自学经历,零基础学java,血泪总结的干货 1.我为什么转行 2.转行之路 3.面试之路 自学路上的网站 Java教程 实践项目一站式学习 bilibili 自学书籍 <Java ...

  2. Java好学吗?零基础入门Java,三个就业方向实现月入过万!

    Java好学吗?零基础入门Java容易吗?据统计,这是很多人学习前最常问也是最关心的问题之一. 不可否认,大家在开始接受新事物的时候都会陷入困境,但学习是循序渐进的,零基础入门Java到底难不难,只有 ...

  3. java思维导图公众号_顿悟|零基础通过12张思维导图学会微信公众号运营?

    我们说,思维导图用来构建知识体系,起到事半功倍的效果: 我们说,进入一个陌生领域的时候,如果能以最快速度建构该领域的基本知识体系,会大大加速自己在领域内的成长速度: 所以,我们说思维导图能助力我们,帮 ...

  4. 零基础java自学就业_java零基础到就业需要多长时间呢?

    展开全部 先以肯定的语气说明一下自学e68a84e8a2ad62616964757a686964616f31333433663030Java,多久可以找到工作: 按照目前Java的体系来说,Java的 ...

  5. Java开发编程小白零基础学习路线附资源

    最新Java零基础小白从入门到精通学习路线: https://www.bilibili.com/read/cv11975482 全套视频资料打包下载(视频.源码.工具.软件.笔记.案例): https ...

  6. 如何自学python爬虫-零基础入门Python爬虫不知道怎么学?这是入门的完整教程

    这是一个适用于小白的Python爬虫免费教学课程,只有7节,让零基础的你初步了解爬虫,跟着课程内容能自己爬取资源.看着文章,打开电脑动手实践,平均45分钟就能学完一节,如果你愿意,今天内你就可以迈入爬 ...

  7. 电脑入门完全自学手册_3DMAX零基础入门到精通的学习路线和教程

    没有美术基础可以学建模吗?毋庸置疑,只要肯学都是可以的!那么我们今天来说说零基础学习建模要着重注意什么.最高效最快速的入门学习方式:看知识兔视频课程,跟着知识兔老师操作,听知识兔老师讲解,初学者不要自 ...

  8. python零基础自学教材-Python零基础入门到精通自学视频教程

    最近,网上流传一组<人工智能实验教材>的图片,照片火起来的原因是教材是为幼儿园的小朋友们设计的Python被列入小学.初高中教材已不是新鲜事,现在又成功"打入"了幼儿园 ...

  9. 零基础自学python教程-零基础入门学习Python_Python教程

    教程名称:零基础入门学习Python 课程目录: [易源码www.pnp8.com]000愉快的开始 [易源码www.pnp8.com]001我和Python的第一次亲密接触 [易源码www.pnp8 ...

最新文章

  1. STM32开发 -- md5sum命令
  2. linux内核死锁检测机制 | oenhan,Linux内核CPU负载均衡机制 | OenHan
  3. jQuery:无限循环两个或者多个事件 click / toggle between two functions
  4. bat递归查找指定文件_批处理脚本遍历指定文件夹下的文件
  5. 马斯克称面临巨大通胀压力 暗示特斯拉可能涨价
  6. es6 Map,Set 和 WeakMap,WeakSet
  7. [APIO2009]抢掠计划 ($Tarjan$,最长路)
  8. IEC 62368认证测试项目
  9. 驱动大师服务器获取信息失败,360驱动大师、鲁大师,连测硬件信息都不准,我拿什么信你们...
  10. 微信小程序js数组倒序reverse
  11. 海康摄像头设置与测试指南
  12. 曾经写过得太监小说2《天道》
  13. Kafka序列化器,分区器,拦截器,消息累加器
  14. 【数学之美】分形图形动画演示欣赏
  15. Ubuntu 18.04刷机问题汇总
  16. autosar的alarm配置
  17. 无法启动程序 系统找不到指定的文件
  18. 安卓平板体验Java开发,还能白嫖一年阿里无影云,真香!(内含白嫖方法,人人可领)...
  19. 独自封装windows 10系统详细教程(一)
  20. python绘画_python绘图骚操作之plotly(一)——plotly的基本绘图方式

热门文章

  1. HTML系列之表格标签
  2. mysql Oracle 的区别 你不能不知道的事
  3. 浙富控股,上柴股份,宁波热电,成交量异动检测
  4. 【自然语言处理入门系列】加载和预处理数据-以Cornell Movie-Dialogs Corpus数据集为例
  5. 单片机检测220v交流电通断
  6. 幽默笑话-笑话-还有菜吗等
  7. html 中avi视频插件,【JDG 2-0 UP 】Kanavi盲僧神准天音波完美针对妖姬,UP双C被限制JDG赢得比赛...
  8. 今天,你的情人节礼物是?
  9. 加盟智能名片行业如何避免被坑?
  10. Android Studio项目模板