java个人整理知识点

基本概念:

发展:

1.SE 标准版。桌面应用(不如c#)、服务器、基础框架。
2.EE 企业版,基于SE。网站应用、大型分布式系统、互联网开发。
3.ME 小型设备、智能卡、嵌入式设备。(不如C语言)

JDK、JRE和JVM:

JDK:

称为Java开发工具包( Java Development Kit)。Java开发人士需要下载和安装JDK,目前的主流版本为JDK11。

JRE:

称之为Java SE运行时环境(Java SE Runtime Environment),提供了运行Java应用程序所必须的软件环境等。无论是开发还是运行Java应用都必须安装。

区别:

JDK包含了JRE,同时还包含了编译java源码的编译器javac,还包含了很多java程序调试和分析的工具:jconsole,jvisualvm等工具软件,还包含了java程序编写所需的文档和demo例子程序。
JRE包含JVM和类库
JVM多个系统上都有实现,使得跨平台能够实现。

javac和java:

javac.exe编译器,主要用于将高级Java源代码翻译成字节码文件。生成对应的xxx.class,即可通过java xxx执行
java.exe解释器,主要用于启动JVM对字节码文件进行解释并执行。新特性:java xxx.java可以直接在版本11运行,如果当前路径存在编译后的class,会报错。

学习路线:

1.java基础

2.javaWeb

servlet

3.常用框架

maven
spring5
springMvc
MyBatis3
Mybatic-Plus
SSM
springboot2

4.微服务

SpringCloud

架构师路线:

1.内功基础

基础算法
数据结构
设计模式 
计算机网络
操作系统 
JVM 

2.核心框架

Java核心源码
    集合 
    JUC 
spring5(设计理念)
springMVC 
springBoot
SpringCloud原生系(openfeign、eureka)
SpringCloud阿里系(nacos、dubbo、sentinel)
Tomcat
Mybatis
netty 
Zookeeper

3.常用中间件

数据库mysql、oracle、hbase
消息rabbitMQ、kafka
检索es
缓存redis
存储minio
调度elastic-job、quartz
新生代etcd、oceanBase数据库、pulsar消息、postgreSQL数据库

4.分布式系统原理与技术

服务注册
服务发现
远程调用
负载均衡
服务熔断
配置管理
链路追踪
认证授权
分布式计算、事务、缓存
服务代理
监控告警

5.云原生

docker、服务编排kubernetes、CI/CD、云平台应用架构与设计、边缘计算

相关书籍:

image-20220218174124291image-20220218174124291
计算机科学导论、计算机组成原理、深入理解计算机系统、计算机网络、现代操作系统
设计模式、数据库系统概念、数据结构与算法分析、算法导论、软件工程、编译原理

基本语法:

1.注释

单行用//,多行用/* */,文档注释/** */可以提取

2.规范

xxx.java开头用注释声明项目名称、功能、作者、版本、备注

3.文件与主类一致

xxx.java里面要有这个类xxx
public class xxx {
    public static void main(String[] args){
    }
}

4.变量的声明方式

数据类型 变量名 = 初始值;    //其中=初始值可以省略,但;不可以省略。
变量使用之前需要声明和初始化。
不能重复声明

5.标识符的命名法则

• 由数字、字母、下划线以及$等组成,其中数字不能开头(不能区分字面值)。
• 不能使用Java语言的关键字,所谓关键字就是Java语言用于表示特殊含义
的单词。
• 区分大小写,长度没有限制但不宜过长。
• 尽量做到见名知意,支持中文但不推荐使用。
• 标识符可以给类/变量/属性/方法/包 起名字。

6.变量的输入

Scanner scanner = new Scanner(System.in);
api的使用可以参考手册index.html

7.import static静态导入:

概述:

一般我们导入一个类都用 import com.....ClassName;
而静态导入是这样:import static com.....ClassName.*;这里的多了个static,还有就是类名ClassName后面多了个 .* ,意思是导入这个类里的静态方法。
当然,也可以只导入某个静态方法,只要把 .* 换成静态方法名就行了。
然后在这个类中,就可以直接用方法名调用静态方法,而不必用ClassName.方法名 的方式来调用。 

用途:

引入类的静态成员,直接使用,不用class.name的方式使用

示例:

System.out.println(Integer.MAX_VALUE); 
可以变为:
    import static java.lang.System.out;
    import static java.lang.Integer.*; 
    out.println(MAX_VALUE);

8.循环import

经试验,可以相互import,某个成员变量如果是相互依赖彼此的方法得出来,那么会拿到默认值。

数据类型:

基本数据类型:

概述:

Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。

1. byte(Byte)

byte 数据类型是8位、有符号的,以二进制补码表示的整数;
最小值是 -128-2^7),最大值是 1272^7-1,最高位为0表示非负),默认值是 0;用在大型数组中节约空间。

2. short(Short)

short 数据类型是 16 位、有符号的以二进制补码表示的整数
最小值是 -32768-2^15);最大值是 327672^15 - 1);默认值是 0

3. int(Integer)

int 数据类型是32位、有符号的以二进制补码表示的整数;
最小值是 -2,147,483,648(-2^31);最大值是 2,147,483,6472^31 - 1);一般地整型变量默认为 int 类型;默认值是 0 ;
正无穷为∞(字节'\u221e'),负无穷为-∞。print输出为Infinity。

4. long(Long)

long 数据类型是 64 位、有符号的以二进制补码表示的整数
默认值是 0L;long a = 100000L,"L"理论上不分大小写,但是若写成"l"容易与数字"1"混淆,不容易分辩。所以最好大写。

默认的数字字面值解释器声明为int类型,然后转换为定义的byteshortint
    但int a=25;byte b=a;解释器会报错,因为a是变量,存储的值还不固定,和数字字面值不同,所以解释器直接报错了。
如果超过int范围,会报错整数太大。需要显式加上l或L。
更大的数可以用java.math.BigInteger
    BigInteger a = new BigInteger("25025050060000000000000000");

5. float(Float)

float 数据类型是单精度、4个字节32位、符合IEEE 754标准的浮点数,可以表示7位有效数字;范围:-3.403E38~3.403E38。(因为有些位数用来表示指数,所以范围更大了)
float 在储存大型浮点数组的时候可节省内存空间;默认值是 0.0f;浮点数不能用来表示精确的值,如货币;
例子:float f1 = 234.5f/F(不加f的话doublefloat,报错)。会自动忽略超过位数。

6. double(Double)(推荐,有效位数更多)

double 数据类型是双精度、8个字节64 位、符合IEEE 754标准的浮点数,可以表示15位有效数字;范围:-1.798E308~1.798E308
浮点数的默认类型为double类型;double类型同样不能表示精确的值,如货币;默认值是 0.0d;运算不精确。比如0.1+0.2=0.3000004
精确可以用java.math.BigDecimal。System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.2")));
例子:double d1 = 123.4

7. boolean

boolean数据类型表示一位的信息;只有两个取值:true 和 false;默认值是 false;内存空间中所占大小没有明确的规定,可以认为是1个字节。

8. char(Character)

char类型是一个单一的 2个字节 16 位(没有符号位) Unicode 字符;
单引号。
最小值是 \u0000(即为0);最大值是 \uffff(即为65,535,UCS-2统一码版本,其中汉字一共2w多个,基本满足各种语言的使用,最新版本UCS-431位字符集);char 数据类型可以储存任何字符;
    扩展:有两个实现utf-8采用变长字节,1-4位字节变化,由于额外的标志信息,4个字节一共64029个(3个字节一共52156个,汉字常用);utf-16两个字节
例子:
    char letter = 'A'。 
    char d=1会自动转换为ascii码对应的字符,计算机的底层只识别01组成的二进制序列。(int)d即可打印对应的数字。

引用数据类型:

分类:

数组、类、接口、枚举、标注

实例化:

new的方式

进制转换:

10转2:

二进制(ob/oB开头)中的最高位(最左边)用于代表符号位,若该位是0则表示非负数, 若该位是1则表示负数。
1.除2取余法,使用十进制整数不断地除以2取出余数,直到商为0时将余数逆序排序。
2.拆分法,将十进制整数拆分为若干个二进制权重(1,2,4,8)的和,有该权重下面 写1,否则写0。

2转10:

1.加权法,使用二进制中的每个数字乘以当前位的权重再累加起来。

负十进制转换为二进制的方式:

先将十进制的绝对值转换为二进制,然后进行按位取反再加1。(涉及到补码的概念,正负相加为0,溢出丢弃)

负二进制转换为十进制的方式:

先减1再按位取反,合并为十进制整数后添加负号。

16进制:

以0x开头

8进制:

以0开头

字节与bit:

公式:
1B(byte,字节)= 8 bit(位)
为什么?
一个字节定义为可容纳单个字符的最小bits
它并不总是8,有时是7或9。这取决于平台。
一个说法是使用8个字符(方便的2乘幂),有时他们将第8位用于奇偶校验,有时又将其用于ASCII标准的扩展。有时他们只是将第8位设置为零。
26个英文字母大小写52个, 加上10个数字,还有一些特殊符号超过了64个,所以ascll128个, 共计需要连续七组信号值, 但是早期的传输不可靠, 于是预留了一位作为奇偶校验, 共八组信号值, 一字节8位

类型转换:

自动类型转换:

从小类型到大类型之间的转换
整型、实型(常量)、字符型数据可以混合运算。运算中,不同类型的数据先转化为同一类型,然后进行运算。
    byte,short,char—> int —> long—> float —> double 

强制类型转换:

概述:
从大类型到小类型之间的转换
格式:
变量名 = (目标类型)源类型变量名;
数据类型转换必须满足如下规则:
// 子类也可以通过这种强制类型转换 变为父类
1. 不能对boolean类型进行类型转换。
2. 不能把对象类型转换成不相关类的对象。
3. 在把容量大的类型转换为容量小的类型时必须使用强制类型转换。
4. 转换过程中可能导致溢出或损失精度,例如:
    int i =128;   
    byte b = (byte)i;
    因为 byte 类型是 8 位,最大值为127,所以当 int 强制转换为 byte 类型时,值 128 时候就会导致溢出。
5. 浮点数到整数的转换是通过舍弃小数得到,而不是四舍五入,例如:
    (int)23.7 == 23;        
    (int)-45.89f == -45

强软弱虚引用:

强引用:

概述:
强引用是最普遍的一种引用,我们写的代码,99.9999%都是强引用:
只要某个对象有强引用与之关联,这个对象永远不会被回收,即使内存不足,JVM宁愿抛出OOM,也不会去回收。
如何回收:
o = null;

软引用:

概述:
当内存不足,会触发JVM的GC,如果GC后,内存还是不足,就会把软引用的包裹的对象给干掉,也就是只有在内存不足,JVM才会回收该对象。
用法:
 SoftReference<Student>studentSoftReference=new java.lang.ref.SoftReference<Student>(new Student());
 Student student = studentSoftReference.get();
适合场景:
读取大量图片,用HashMap保存图片的路径和相应图片对象关联的软引用的映射,内存不足时,自动回收掉图片对象。避免OOM。
mybatics缓存机制

弱引用:

概述:
弱引用的使用和软引用类似,只是关键字变成了WeakReference:
弱引用的特点是不管内存是否足够,只要发生GC,都会被回收:
用法:
WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1024*1024*10]);
weakReference.get()
System.gc()
WeakHashMap:
概述:
java.utils.
普通的HashMap会将key的引用复制一份。
key被回收后,entry会被移除

虚引用:

概述:
无法通过虚引用来获取对一个对象的真实引用。
虚引用必须与ReferenceQueue一起使用,当GC准备回收一个对象,如果发现它还有虚引用,就会在回收之前,把这个虚引用加入到与之关联的ReferenceQueue中。
    Reference poll = queue.poll();可以接收到。
    当发生GC,虚引用就会被回收,并且会把回收的通知放到ReferenceQueue中。
场景:
主要用来跟踪对象被垃圾回收器回收的活动,在所引用的对象的内存被回收之前采取必要的行动。
用来实现比finaliztion机制更灵活的回收操作。
用法:
ReferenceQueue queue = new ReferenceQueue();
        PhantomReference<byte[]> reference = new PhantomReference<byte[]>(new byte[1], queue);
        Reference poll = queue.poll();
引用队列:
概述:
可以与虚引用、弱引用、软引用一起使用,引用回收后放入队列中。

运算符:

算术运算符:

表示加法运算符,同时可以实现字符串(至少一边即可)与其他数据类型“相连”。(表明java为弱类型语言)
表示减法运算符
表示乘法运算符
/ 表示除法运算符,只保留整数部分,要想保留小数需要其中一个为double,或者乘以0.1。不能除以0,除以0.0得到infinity,0/0得到NaN
% 表示取模/取余运算符

自增减运算符:

只能用于变量,常数不可以
a++:是一个表达式,先让a的数值作为整个表达式的最终结果,然后再让a变量加1
++:a让a变量加1,再让a的数值作为整个表达式的最终结果。少了一步声明赋值,效率更高。
a--
--a

逻辑运算符:

&& 表示逻辑与运算符,相当于"并且",同真为真,一假为假。 
|| 表示逻辑或运算符,相当于"或者",一真为真,同假为假。
! 表示逻辑非运算符,相当于"取反",真为假,假为真。
逻辑运算符的操作数均为boolean表达式。

短路特性
对于逻辑与运算符来说,若第一个表达式为假则结果为假,此时跳过第 二个表达式;
对于逻辑或运算符来说,若第一个表达式为真则结果为真,此时跳过第 二个表达式;

条件/三目运算符:

条件表达式? 表达式1: 表达式2
判断条件表达式是否成立,若成立则执行表达式1,否则执行表达式2 。

赋值运算符:

= 表示赋值运算符,用于将=右边的数据赋值给=左边的变量,覆盖变量 原来的数值。
赋值表达式本身也有值,其本身之值即为所赋之值。
+=、 -=、 *=、 /=、 ...

注意:
    byte a=10;
    a = a+10;会报错,编译器做了优化,将a和10都变为int类型然后相加,结果为int类型。需要强转换。
    而a+=10等价于a=(byte)(a+10),所以没有报错。

规范:
    a==2
    2==a两者的区别是少写一个等号的时候,能在编译阶段报错。

移位运算符:

<< 左移运算符,用于将数据的二进制位向左移动,右边使用0补充
    左移1位通常结果*2,在没超出范围的情况下。
>> 右移运算符,用于将数据的二进制位向右移动,左边使用符号位补充
    右移1位相当于除2
>>> 表示逻辑右移运算符,用于将数据的二进制位向右移动,左边使用0 补充。
    非负时和右移一样

位运算符:

& 表示按位与运算符,按照二进制位进行与运算,同1为1,一0为0.
| 表示按位或运算符,按照二进制位进行或运算,一1为1,同0为0.
~ 表示按位取反运算符,按照二进制位进行取反,1为0,0为1.
^ 表示按位异或运算符,按照二进制位进行异或运算,同为0,不同为1.

优先级:

[]()(方法调用)                从左到右
!~++--+(一元运算)-(一元运算)    从右到左
*/%                            从左到右
+-                            从左到右
<< >> >>>                    从左到右
== !=                        从左到右
&                            从左到右
^                            从左到右
|                            从左到右
&&                            从左到右
||                            从左到右
?:                            从右到左
=                            从右到左

流程控制:

if语句:

if(){}
if else 
if else if else

switch case分支结构:

从上到下执行
switch(变量/表达式) {
    case 字面值1: 语句块1break
    case 字面值2: 语句块2break; ...
    default:语句块n;
}
switch()中支持的数据类型有:byteshortchar以及int类型,从jdk1.5 开始支持枚举类型,从jdk1.7开始支持String类型。
case穿透:如果执行了一个case没有break,会执行下一行直到break
default和位置顺序无关,总是在匹配不到的时候才执行。

for循环:

for(初始化表达式; 条件表达式; 修改初始值表达式) { 循环体;
}
for(;;) - 这种没有循环条件的循环叫做 无限循环,俗称“死循环”。
如果要退出外层循环体,需要使用标号的方式。
    outer: for (...) {
        for(...) {
            break outer;
        }
    }

while循环:

while(条件表达式) { 
    循环体;
}
注意:
    while(条件表达式);{}相当于
    while(条件表达式){:}{},这会导致i不会增加, 空语句,用于延时。

do while循环(熟悉):

do { 
    循环体;
while(条件表达式);        //这里有个分号

数组:

概述:

Java 语言中提供的数组是用来存储固定大小的同类型元素。

声明:

首先必须声明数组变量,才能在程序中使用数组。
dataType[] arrayRefVar;   // 首选的方法
dataType arrayRefVar[];  // 效果相同,但不是首选方法,dataType arrayRefVar[] 风格是来自 C/C++ 语言 ,在Java中采用是为了让 C/C++ 程序员能够快速理解java语言。

创建:

dataType[] arrayRefVar = new dataType[arraySize];
dataType[] arrayRefVar = {value0, value1, ..., valuek};
dataType[] arrayRefVar = new dataType[]{value0, value1, ..., valuek};
示例:
    int[] arr = new int[2];默认值填充

索引:

数组的元素是通过索引访问的。数组索引从 0 开始,所以索引值从 0 到 arrayRefVar.length-1

遍历:

for (int i = 1; i < myList.length; i++)
for(type element: array)

内存结构分析:

栈区
    栈用于存放程序运行过程当中所有的局部变量。一个运行的Java程序从开 始到结束会有多次变量的声明。
堆区
    JVM会在其内存空间中开辟一个称为“堆”的存储空间,这部分空间用于存 储使用new关键字创建的数组和对象。
    示例:
        int[] a = new int[2];这句话,先在栈区声明一个变量,然后在堆区初始化数组,最后赋值操作是在栈区存储堆区的内存地址(引用类型)

增删改查:

数组赋值arr=brr,会把brr的内存地址赋值给arr,arr的长度元素都变为brr。
拷贝:System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

优缺点:

可以直接通过下标(或索引)的方式访问指定位置的元素,速度很快。 
数组要求所有元素的类型相同。
数组要求内存空间连续,并且长度一旦确定就不能修改。
增加和删除元素时可能移动大量元素,效率低。

Arrays 类:

java.util.Arrays 类能方便地操作数组(遍历、查找、排序),它提供的所有方法都是静态的。
方法:
    static String toString(int[] a)
    public static void fill(int[] a, int val)   //将指定的 int 值分配给指定 int 型数组指定范围中的每个元素。
        示例:
            Arrays.fill(a, 1,3,10)
; 将13不含3的位置的元素填充为10,注意不能超索引
    public static void sort(Object[] a)     //对指定对象数组根据其元素的自然顺序进行升序排列。
    public static boolean equals(long[] a, long[] a2)   //如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。返回true
    public static int binarySearch(Object[] a, Object key)  //用二分查找算法在给定数组中搜索给定值的对象(Byte,Int,double等)。
            //数组在调用前必须排序好的(二分查找)。如果查找值包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
    copyOfRange(li,start,end)切片复制,生成另一个数组

二维数组:

声明和初始化:

数据类型[][] 数组名称 = new 数据类型[行数][列数];         其中列数可以忽略。表示不定长。
数据类型[][] 数组名称 = {{元素1, 元素2,...}, ...};

面向对象编程:

概念:

面向对象指以属性和行为的观点去分析现实生活中的事物。
面向对象编程指先以面向对象的思想进行分析,然后使用面向对象的编程语言进行表达的过程。
面向过程指侧重具体的步骤,面向对象则是针对能实现步骤的对象。

理解面向对象的思想精髓(封装、继承、多态),至少掌握一种编程语言。

类和对象的概念:

类简单来就是“分类”,是对具有相同特征和行为的多个对象共性的抽象描述,在Java语言中体现为一种引用数据类型,里面包含了描述特征/属性的成员变量以及描述行为的成员方法。
类是用于构建对象的模板,对象的数据结构由定义它的类来决定。

类和成员变量的定义:

class 类名{        通常情况下,当类名由多个单词组成时,要求每个单词首字母都要大写。
    类体;
    数据类型成员变量名= 初始值;     当成员变量由多个单词组成时,通常要求从第二个单词起每个单词的首字母大写。 初始值一般不写
}

对象的创建:

a.当一个类定义完毕后,可以使用new关键字来创建该类的对象,这个过程叫做类的实例化。
b.创建对象的本质就是在内存空间的堆区申请一块存储区域,用于存放该对象独有特征信息。
成员变量的默认值: 
    当变量作为作为类成员使用时,java才确保给定其初始值,防止程序运行时错误。
    byte short int long float double char  0/'/uoooo'
    boolean false 
    引用类型 null

引用的定义:

基本概念:

a.使用引用数据类型定义的变量叫做引用型变量,简称为"引用"
b.引用变量主要用于记录对象在堆区中的内存地址信息,便于下次访问。

语法格式:

类名引用变量名;
引用变量名.成员变量名;

方法:

定义:

class 类名{
    返回值类型 成员方法名(形参列表) {     当成员方法名由多个单词组成时,要求从第二个单词起每个单词的首字母大写
        成员方法体;
    }
}    
方法内访问成员可以直接访问。
数据类型形参变量名1, 数据类型形参变量名2, ...

调用:

引用变量名.成员方法名(实参列表);

可变长参数:

返回值类型方法名(参数的类型... 参数名) 类型固定,看作数组即可。
方法参数部分指定类型的参数个数是可以改变的,也就是0~n个。
一个方法的形参列表中最多只能声明一个可变长形参,并且需要放到参数列表的末尾。
基于数组的实现的语法糖

参数传递:

Java 编程语言只有值传递参数。
基本数据类型的变量作为方法的参数传递时,形参变量数值的改变通常不会影响到实参变量的数值,因为两个变量有各自独立的内存空间;
引用数据类型的变量作为方法的参数传递时,形参的值就是该对象引用的一个副本,指向内容的改变会影响到实参变量指向内容的数值,因为两个变量指向同一块内存空间。
    但如果改变了形参的引用,对象的引用(不是引用的副本)是永远不会改变的,因为两个变量指向不同的内存空间。
形参和实参不能同名,否则当作形参了。

内存结构:

栈用于存放程序运行过程当中所有的局部变量。一个运行的Java程序从开始到结束会有多次方法的调用。
JVM会为每一个方法的调用在栈中分配一个对应的空间,这个空间称为该方法的栈帧。一个栈帧对应一个正在调用中的方法,栈帧中存储了该方法的参数、局部变量等数据。
当某一个方法调用完成后,其对应的栈帧将被清除。

构造方法:

class 类名{
    类名(形参列表) {
        构造方法体;
    }
}
构造方法名与类名完全相同并且没有返回值类型,连void都不许有。
支持重载。
使用new关键字创建对象时会自动调用构造方法实现成员变量初始化工作。
this可以在构造方法里调用其他构造方法
默认构造方法:
    当一个类中没有定义任何构造方法时,编译器会自动添加一个无参空构造构造方法,叫做默认/缺省构造方法,如:Person(){}
    若类中出现了构造方法,则编译器不再提供任何形式的构造方法。

方法引用:

方法引用通过方法的名字来指向一个方法,可以使语言的构造更紧凑简洁,减少冗余代码。
使用一对冒号 :

1. 构造器引用:它的语法是Class::new,或者更一般的Class< T >::new实例如下:
    final Car car = Car.create( Car::new );
    final List< Car > cars = Arrays.asList( car );

2. 静态方法引用:它的语法是Class::static_method,实例如下:
    cars.forEach( Car::collide );

3. 特定类的任意对象的方法引用:它的语法是Class::method实例如下:
    cars.forEach( Car::repair );

4. 特定对象的方法引用:它的语法是instance::method实例如下:
    final Car police = Car.create( Car::new );
    cars.forEach( police::follow );

方法重载:

若方法名称相同,参数列表(类型、个数、顺序)不同,这样的方法之间构成重载关系(Overload)。
和返回类型无关。
实际意义
    调用者只需要记住一个方法名就可以调用各种不同的版本,来实现各种不同的功能。

this关键字:

若在构造方法中出现了this关键字,则代表当前正在构造的对象。可以print验证。
若在成员方法中出现了this关键字,则代表当前正在调用的对象。
this关键字本质上就是当前类类型的引用变量。
工作原理:
    在构造方法中和成员方法中访问成员变量时,编译器会加上this.的前缀,而this.相当于汉语中"我的"
    当不同的对象调用同一个方法时,由于调用方法的对象不同导致this关键字不同,从而this.方式访问的结果也就随之不同。
使用方式:
    1.当局部变量名与成员变量名相同时,在方法体中会优先使用局部变量(就近原则),若希望使用成员变量,则需要在成员变量的前面加上this.的前缀,明确要求该变量是成员变量(重中之重)。
    2.this关键字除了可以通过this.的方式调用成员变量和成员方法外,还可以作为方法的返回值(重点)。
    3.在构造方法的第一行可以使用this()的方式来调用本类中的其它构造方法(了解)。

递归:

使用递归必须有递归的规律以及退出条件。
使用递归必须使得问题简单化而不是复杂化。
若递归影响到程序的执行性能,则使用递推取代之。比如费氏数列

代码拆分:

默认导入同级的classmain方法单独放到xxxTest.java当中。

封装:

私有化成员变量,使用private关键字修饰。
提供公有的get和set方法,并在方法体中进行合理值的判断。
在构造方法中调用set方法进行合理值的判断。
JavaBean
    一种Java语言写成的可重用组件,其它Java 类可以通过反射机制发现和操作这些JavaBean 的属性。
    JavaBean本质上就是符合以下标准的Java类:
        类是公共的
        有一个无参的公共的构造器
        有属性,且有对应的getset方法

static关键字:

基本概念:

使用static关键字修饰成员变量表示静态的含义,此时成员变量由对象层级提升为类层级,也就是整个类只有一份并被所有对象共享,该成员变量随着类的加载准备就绪,与是否创建对象无关。
static关键字修饰的成员可以使用引用.的方式访问,但推荐类名.的方式。    

使用方式:

在非静态成员方法中既能访问非静态的成员又能访问静态的成员。(成员:成员变量+ 成员方法,静态成员被所有对象共享)
在静态成员方法中只能访问静态成员不能访问非静态成员。(成员:成员变量+ 成员方法,因为此时可能还没有创建对象)
    静态变量可以通过静态方法修改访问
在以后的开发中只有隶属于类层级并被所有对象共享的内容才可以使用static关键字修饰。(不能滥用static关键字)

内存结构:

静态变量位于方法区(代码区)。指向可能是堆区(单例模式的对象)。

单例设计模式:

流程:
1.私有化构造方法,使用private关键字修饰。
2.声明本类类型的引用指向本类类型的对象,并使用private static关键字共同修饰。
3.提供公有的get方法负责将对象返回出去,并使用public static关键字共同修饰。
实现方式:
饿汉式和懒汉式(if null==xxx再创建),在以后的开发中推荐饿汉式(避免多线程问题)。
双重检查锁
    问题:
        并发场景下,初始化Singleton 和 将对象地址写到instance字段 的顺序是不确定的。
        在某个线程new Singleton()时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了。
        然而该对象可能还没有初始化(初始化赋值的业务逻辑)。此时若另外一个线程来调用getInstance,取到的就是状态不正确的对象。(比如一些变量还没赋值完成。))
双重检查锁 + volatile
    禁止未初始化完成时,就将内存地址赋值给instance字段。

构造块和静态代码块:

构造块:

在类体中直接使用{}括起来的代码块。每创建一个对象都会执行一次构造块。

静态代码块:

使用static关键字修饰的构造块。静态代码块随着类加载时执行一次。先于构造块执行(加载阶段),比如加载数据库的驱动包等。

子类构造时执行顺序:

先加载父类,再加载子类,在执行父类的无参构造方法(会先执行构造块),再执行子类的构造方法

继承:

概念:

当多个类之间有相同的特征和行为时,可以将相同的内容提取出来组成一个公共类,让多个类吸收公共类中已有特征和行为而在多个类型只需要编写自己独有特征和行为的机制,叫做继承。

格式:

在Java语言中使用extends(扩展)关键字来表示继承关系。
public class Worker extends Person{} -表示Worker类继承自Person类
    其中Person类叫做超类、父类、基类。
    其中Worker类叫做派生类、子类、孩子类。
使用继承提高了代码的复用性,可维护性及扩展性,是多态的前提条件。

特点:

子类不能继承父类的构造方法和私有方法,但私有成员变量可以被继承只是不能直接访问(可以通过public方法来访问)。
无论使用何种方式构造子类的对象时,都会首先自动调用父类的无参构造方法(没有super的情况下),来初始化从父类中继承的成员变量以及自己的单独成员变量(super()方法做的事情)。
相当于在构造方法的第一行增加代码super()的效果。
    可以手动加super()则解释器不再加(不是第一行会报错)。
    也可以super(实参列表),这样就不再调用父类的无参构造方法
    如果子类不定义任何构造方法,如果父类定义有参,需要父类定义无参构造方法以便super调用。
    如果子类定义了无参构造方法,也需要父类定义无参构造方法以便super隐式或者显式调用。
    如果子类定义了有参构造方法,可以不定义无参,需要父类定义有参或者无参构造方法,以便super隐式或者显式调用。
    总结:一般都定义比较好。
Java语言中只支持单继承不支持多继承,也就是说一个子类只能有一个父类,但一个父类可以有多个子类。

方法重写:

从父类中继承下来的方法不满足子类的需求时,就需要在子类中重新写一个和父类一样的方法来覆盖从父类中继承下来的版本,该方式就叫做方法的重写(Override)
调用父类方法:
    super.function()
示例: 
    @override           # 说明重写,若没有重写则编译报错。
    public void show(){
        super.function()
        xxx
    }
原则:
    要求方法名相同、参数列表相同以及返回值类型相同,从Java5开始允许返回类型为子类类型。
    要求方法的访问权限不能变小,可以相同或者变大。(继承本身是扩展,不能变小)
    要求方法不能抛出更大的异常(异常机制)。
静态方法: 
    不能标注@override,但可以起同名的。

访问控制符:

public : 对所有类可见。使用对象:类、接口、变量、方法
protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
default (即默认): 在同一包内可见(不同包内的子类不行),不使用任何修饰符。使用对象:类、接口、变量、方法。
private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)

package包:

格式:

package 包名;
package 包名1.包名2.包名3...包名n;

规范:

org.apache.commons.lang.StringUtil
其中StringUtils是类名而org.apache.commons.lang是多层包名,其含义如下:
org.apache表示公司或组织的信息(是这个公司(或组织)域名的反写);common 表示项目的名称信息;lang 表示模块的名称信息。    

导入:

使用import关键字导入包。
使用import关键字导入一个包的静态成员,从Java5.0开始支持。

final关键字:

final关键字修饰类体现在该类不能被继承。
    -主要用于防止滥用继承,如:java.lang.String类等。
final关键字修饰成员方法体现在该方法不能被重写但可以被继承(即只能调用)。
    -主要用于防止不经意间造成重写,如:java.text.Dateformat类中format方法等。
final关键字修饰成员变量体现在该变量必须初始化(一开始显式初始化或者延迟在构造块/方法中初始化)且不能改变。
    -主要用于防止不经意间造成改变,如:java.lang.Thread类中MAX_PRIORITY等。

常量:

在以后的开发中很少单独使用final关键字来修饰成员变量
通常使用public static final关键字共同修饰成员变量来表达常量的含义,常量的命名规范要求是所有字母都要大写,不同的单词之间采用下划线连。
    public static 修饰的常量作用域是全局的,不需要创建对象就可以访问它

多态:

格式:

父类类型引用变量名= new 子类类型();
如Shape sr= new Rect();

特点:

当父类类型的引用指向子类类型的对象时,父类类型的引用可以直接调用父类独有的方法,不可以直接调用子类独有的方法(编译会报错,需要强转)。        
对于父子类都有的非静态方法来说,编译阶段调用父类版本,运行阶段调用子类重写的版本(动态绑定)(如果子类没有则调用父类)。
对于父子类都有的静态方法来说,编译和运行阶段都调用父类版本。

引用数据类型之间的转换:

引用数据类型之间的转换方式有两种:自动类型转换和强制类型转换。
    自动类型转换主要指小类型向大类型的转换,也就是子类转为父类,也叫做向上转型。(多态调用)
    强制类型转换主要指大类型向小类型的转换,也就是父类转为子类,也叫做向下转型或显式类型转换。
条件:
    1.引用数据类型之间的转换必须发生在父子类之间,否则编译报错。
    2.如果转换发生在父级和兄弟子类或者孙类这种不是父子关系时,编译阶段不报错,但运行阶段会报ClassCastException。
        若强转的目标类型并不是该引用真正指向的数据类型时则编译通过,运行阶段发生类型转换异常。
使用:
    为了避免上述错误的发生,应该在强转之前进行判断,格式如下:
        if(引用变量instanceof数据类型)
    判断引用变量指向的对象是否为后面的数据类型

实际意义:

屏蔽不同子类的差异性实现通用的编程带来不同的效果。

多态存在的三个必要条件:

1.继承
2.重写
3.父类引用指向子类对象 //Parent p = new Child();

多态的实现方式:

方式一:重写
方式二:接口
方式三:抽象类和抽象方法    

多态的使用场景:

1.通过方法的参数传递形成多态
2.抽象类或接口类的引用指向子类,在方法体中直接使用多态的语法格式
3.通过方法的返回值类型形成多态

虚函数:

虚函数的存在是为了多态。
Java 中其实没有虚函数的概念,它的普通函数就相当于 C++ 的虚函数,动态绑定是Java的默认行为。
如果 Java 中不希望某个函数具有虚函数特性,可以加上 final 关键字变成非虚函数。
重写: 
    当子类对象调用重写的方法时,调用的是子类的方法,而不是父类中被重写的方法。要想调用父类中被重写的方法,则必须使用关键字 super
示例:    
    Salary s = new Salary("员工 A""北京"33600.00);
    Employee e = new Salary("员工 B""上海"22400.00);
    在编译的时候,编译器使用 Employee 类中的 mailCheck() 方法验证该语句, 
    但是在运行的时候,Java虚拟机(JVM)调用的是 Salary 类中的 mailCheck() 方法
    以上整个过程被称为虚拟方法调用,该方法被称为虚拟方法。
    Java中所有的方法都能以这种方式表现,因此,重写的方法能在运行时调用,不管编译的时候源代码中引用变量是什么数据类型。 

抽象方法:

概述:

主要指不能具体实现的方法并且使用abstract关键字修饰,也就是没有方法体。

格式:

访问权限 abstract 返回值类型 方法名(形参列表);

兼容的访问权限:

不能private,私有方法不能继承
不能final,需要修改。
不能static,抽象方法不能提升为类层级
default需要同一包内。

抽象类:

概念:

主要指不能具体实例化的类并且使用abstract关键字修饰,也就是不能创建对象。
和普通类的区别:
    不能具体实例化
    声明了抽象方法。

特点:

抽象类中可以有成员变量、构造方法、成员方法(供子类super调用)
抽象类中可以没有抽象方法,也可以有抽象方法(抽象类不能实例化,防止误调用抽象方法);
拥有抽象方法的类必须是抽象类
因此真正意义上的抽象类应该是具有抽象方法(不能实例化的意义所在)并且使用abstract关键字修饰的类。

实际意义:

抽象类的实际意义不在于创建对象而在于被继承。
当一个类继承抽象类后必须重写抽象方法,否则该类也变成抽象类,也就是抽象类对子类具有强制性和规范性,因此叫做模板设计模式。

格式:

public abstract class Employee
{
   public abstract double computePay();
   //其余代码
}

接口:

概述:

一种比抽象类还抽象的类,体现在所有方法都为抽象方法。

定义:

定义类的关键字是class,而定义接口的关键字是interface
里面只能声明常量,可以忽略public static final 
从jdk1.9开始允许接口出现私有方法。
方法可以忽略public abstract

支持的修饰符:

public 
private

实际意义:

类可以实现多个接口,达到多继承的效果

格式:

Interface关键字用来声明一个接口。
[可见度] interface 接口名称 [extends 其他的接口名] 
{
        // 声明变量
        // 抽象方法
}

使用:

implements A,B 支持多实现,接口之间可以继承。
然后
    A xxx = new yyy();可以调用A接口的方法
    B xxx = new yyy();可以调用B接口的方法

标记接口:

最常用的继承接口是没有包含任何方法的接口。
标记接口是没有任何方法和属性的接口.它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情。
标记接口作用:简单形象的说就是给某个对象打个标(盖个戳),使对象拥有某个或某些特权。
    public interface EventListener
    {}
没有任何方法的接口被称为标记接口。标记接口主要用于以下两种目的:
    1. 建立一个公共的父接口:
        正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。
        例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。
    2. 向一个类添加数据类型:
        这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。        

类和接口之间的关系:

名称                            关键字                         关系
类和类之间的关系          使用extends关键字表达继承关系          支持单继承
类和接口之间的关系         使用implements关键字表达实现关系   支持多实现
接口和接口之间的关系     使用extends关键字表达继承关系      支持多继承

抽象类和接口的主要区别:

定义抽象类的关键字是abstract class,而定义接口的关键字是interface
继承抽象类的关键字是extends,而实现接口的关键字是implements
继承抽象类支持单继承,而实现接口支持多实现。(区别3
抽象类中可以有构造方法,而接口中不可以有构造方法。(区别1
抽象类中可以有成员变量,而接口中只可以有常量。(区别2
抽象类中可以有成员方法,而接口中只可以有抽象方法。
抽象类中增加方法时子类可以不用重写,而接口中增加方法时实现类需要重写(Java8以前的版本)。
从Java8开始增加新特性,接口中允许出现非抽象方法和静态方法(工具类功能),但非抽象方法需要使用default关键字修饰(表示接口的默认功能方法,子类可以选择性重写)。
从Java9开始增加新特性,接口中允许出现私有方法。(一般用于定义一些基础方法,供defaultstatic方法调用)

函数式接口:

一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。
如:
    @FunctionalInterface
    interface GreetingService 
    {
        void sayMessage(String message);
    }
那么就可以使用Lambda表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的):
    GreetingService greetService1 = message -> System.out.println("Hello " + message);

内部类:

基本概念:

当一个类的定义出现在另外一个类的类体中时,那么这个类叫做内部类(Inner),而这个内部类所在的类叫做外部类(Outer)。
# 类中的内容:成员变量、成员方法、构造方法、静态成员、构造块和静态代码块、内部类。

实际作用:

当一个类存在的价值仅仅是为某一个类单独服务时,那么就可以将这个类定义为所服务类中的内部类,这样可以隐藏该类的实现细节并且可以方便的访问外部类的私有成员而不再需要提供公有的getset方法。

分类:

普通内部类-直接将一个类的定义放在另外一个类的类体中。
静态内部类-使用static关键字修饰的内部类,隶属于类层级。
局部内部类-直接将一个类的定义放在方法体的内部时。
匿名内部类-就是指没有名字的内部类。

普通(成员)内部类:

定义:
属于外部类的一个成员,能直接调用外部类的其他成员
特点:
普通内部类和普通类一样可以定义成员变量、成员方法以及构造方法等。
普通内部类和普通类一样可以使用final(限制内部类的继承)或者abstract关键字修饰。
普通内部类还可以使用private(不能用于实例化)或protected关键字进行修饰。
普通内部类需要使用外部类对象来创建对象。Outer.Inner in = ot.new Inner();
如果内部类访问外部类中与本类内部同名的成员变量或方法时,需要使用this关键字。(遵循局部优先原则,this/Outer.this分别访问内部和外部对象)

静态内部类:

格式:
访问修饰符 static class 内部类的类名
使用方式:
静态内部类不能直接访问外部类的非静态成员,可以直接访问静态成员。
静态内部类可以直接创建对象。Outer.Inner in = new Outer.Inner();
如果静态内部类访问外部类中与本类内同名的成员变量或方法时,需要使用类名.的方式访问。
加载时机:
主类加载的时候不会立刻加载静态内部类,而是等到有引用的时候才会加载(比如传参、返回)
应用:
单例

局部(方法)内部类:

格式:
class 内部类的类名
没有修饰符(类似局部变量)
使用方式:
局部内部类只能在该方法的内部可以使用。
局部内部类可以在方法体内部直接创建对象。
局部内部类不能使用访问控制符和static关键字修饰符。
局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致。
    默认的局部变量从java8开始默认理解为final关键字修饰的变量,如果修改了,会报错。
    原因是防止局部变量拷贝到内部类中修改了,造成内外不一致。(防止修改)

回调模式:

如果一个方法的参数是接口类型,则在调用该方法时,需要创建并传递一个实现此接口类型的对象;而该方法在运行时会调用到参数对象中所实现的方法(接口中定义的)。
使用: 
    自定义类实现接口/继承类并重写方法,然后创建该类对象作为实参传递;
    使用上述匿名内部类的语法格式得到接口/类类型的引用即可;

匿名内部类:

格式:
接口/父类类型 引用变量名= new 接口/父类类型() {方法声明、重写};
在函数式接口的情况下,使用lambda表达式更简单()->{}
优点:
省去了为类起名字的烦恼

lambda表达式:

概述:

声明一个函数
一种没有声明的方法,即没有访问修饰符,返回值声明和名称。(但是可以返回,类型推断机制)

用途:

1.简写函数式接口(Functional Interface)
2.声明函数,用于其他高阶函数接收,比如列表foreach,字典map

限制:

不能使用闭包
lambda表达式中对变量的操作都是基于原变量的副本,不会影响到原变量的值。
只能引用标记了 final 的外层局部变量,否则会误以为外部变量的值能够在lambda表达式中被改变。

更先进的写法:

双冒号操作符

枚举:

概述:

在日常生活中这些事物的取值只有明确的几个固定值,此时描述这些事物的所有值都可以一一列举出来,而这个列举出来的类型就叫做枚举类型。

之前的实现方法:

class中定义多个常量,给外部访问。
缺点:
    重复声明比较多

定义:

•使用public static final表示的常量描述较为繁琐,使用enum关键字来定义枚举类型取代常量,枚举类型是从Java5开始增加的一种引用数据类型。
•枚举值就是当前类的类型,也就是指向本类的对象,默认使用public static final关键字共同修饰,因此采用枚举类型.的方式调用。
•枚举类可以自定义构造方法,但是构造方法的修饰符必须是private,默认也是私有的。

示例:

public enum DirectionEnum {
    UP("up"),DOWN("down");

    private final String desc;

    private DirectionEnum(String desc) {
        this.desc = desc;
    }
}

switch使用:

function(Enum de){
    switch(de){
        case UP:
        case DOWN:
    }
}
更简洁,对输入类型更限制

Enum类:

概念:
所有的枚举类都继承自java.lang.Enum
方法:
static T[] values()返回当前枚举类中的所有对象
String toString()返回当前枚举类对象的名称
intordinal()获取枚举对象在枚举类中的索引位置
static T valueOf(String str)将参数指定的字符串名转为当前枚举类的对象,要求字符串必须对应存在
intcompareTo (E o)比较两个枚举对象在定义时的顺序,结果为10-1-2等,当调用对象在参数对象之后时,获取到的比较结果为正数。

实现接口:

不能继承,可以实现。
枚举类实现接口后需要重写抽象方法,而重写方法的方式有两种:重写一个,或者每个对象都重写。
每个对象都重写:
    UP("up"){重写方法}
    本质上是匿名内部类

注解:

基本概念:

注解(Annotation)又叫标注,是从Java5开始增加的一种引用数据类型。
注解本质上就是代码中的特殊标记,通过这些标记可以在编译、类加载、以及运行时执行指定的处理。

语法格式:

访问修饰符@interface 注解名称{
    注解成员;
}
自定义注解自动继承java.lang.annotation.Annotation接口。
若一个注解中没有任何的成员,则这样的注解叫做标注注解/标识注解。
注解体中只有成员变量没有成员方法,而注解的成员变量以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
如果注解只有一个参数成员,建议使用参数名为value,而类型只能是八种基本数据类型、String类型、Class类型、enum类型及Annotation类型。
成员变量可以有默认值:private String value() default "";
如: 
    public String value();

使用:

通过@注解名称的方式可以修饰包、类、成员方法、成员变量、构造方法、参数、局部变量的声明等。
@MyAnnotation(value=xxx,value2=yyy)

元注解:

概述:
元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。
分类:
元注解主要有@Retention、@Documented、@Target、@Inherited、@Repeatable。
元注解@Retention:
•@Retention 应用到一个注解上用于说明该注解的的生命周期,取值如下:
•RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
•RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到JVM 中,默认方式。
•RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到JVM 中,所以在程序运行时可以获取到它们。
元注解@Documented
•使用javadoc工具可以从程序源代码中抽取类、方法、成员等注释形成一个和源代码配套的API帮助文档,而该工具抽取时默认不包括注解内容。
•@Documented用于指定被该注解将被javadoc工具提取成文档。
•定义为@Documented的注解必须设置Retention值为RUNTIME。
元注解@Target
用于指定被修饰的注解能用于哪些元素的修饰
ElementType.ANNOTATION_TYPE可以给一个注解进行注解
ElementType.CONSTRUCTOR可以给构造方法进行注解
ElementType.FIELD可以给属性进行注解
ElementType.LOCAL_VARIABLE可以给局部变量进行注解
ElementType.METHOD可以给方法进行注解
ElementType.PACKAGE可以给一个包进行注解
ElementType.PARAMETER可以给一个方法内的参数进行注解
ElementType.TYPE可以给类型进行注解,比如类、接口、枚举
从Java8开始对元注解@Target的参数类型ElementType枚举值增加了两个:
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中,如:泛型。
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。场景更广泛。
元注解@Inherited:
@Inherited并不是说注解本身可以继承,而是说如果一个超类被该注解标记过的注解进行注解时,如果子类没有被任何注解应用时,则子类就继承超类的注解。
元注解@Repeatable:
表示自然可重复的含义,从Java8开始增加的新特性。
示例: 
    @Repeatable(value=ManTypes.class)
默认不能重复。java8之前可以通过注解数组的方式@manTypes({@ManType(value=""),@ManType(value="")})

常见的预制注解:

@author标明开发该类模块的作者,多个作者之间使用,分割
@version标明该类模块的版本
@see参考转向,也就是相关主题
@since从哪个版本开始增加的
@param对方法中某参数的说明,如果没有参数就不能写
@return对方法返回值的说明,如果方法的返回值类型是void就不能写
@exception对方法可能抛出的异常进行说明    
@Override限定重写父类方法, 该注解只能用于方法
@Deprecated用于表示所修饰的元素(类, 方法等)已过时
@SuppressWarnings抑制编译器警告

Object类:

基本概念:

java.lang.Object类是Java语言中类层次结构的根类,也就是说任何一个类都是该类的直接或者间接子类。
如果定义一个Java类时没有使用extends关键字声明其父类,则其父类为 java.lang.Object 类。
Object类定义了“对象”的基本行为, 被子类默认继承。

常用的方法:

Object() 使用无参方式构造对象
boolean equals(Object obj)用于判断调用对象是否与参数对象相等。该方法默认比较两个对象的地址是否相等,与 == 运算符的结果一致若希望比较两个对象的内容,则需要重写该方法。
    若该方法被重写后,则应该重写hashCode方法来保证结果的一致性(不然放到字典中时,两个相等的对象存的位置不同)。
    需要强转来调用子类的独有方法。
    自反性、对称的、传递性、一致的、对于任何非空的参考值x , x.equals(null)应该返回false。 
    优化:需要判断是否地址相同,是否为null
int hashCode()用于获取调用对象的哈希码值(内存地址的编号)。
    若两个对象调用equals方法相等,则各自调用该方法的结果必须相同。
    若两个调用对象equals方法不相等,则各自调用该方法的结果应该不相同。
    为了使得该方法与equals方法保持一致,需要重写该方法。一般加个固定值。不然很可能会因为存储了两个equals相等的数据而导致存储数据的不唯一性,导致内存泄露。
    非基本类型可以用对应的引用类型的hashCode方法
String toString()用于获取调用对象的字符串形式。该方法默认返回的字符串为:包名.类名@哈希码值的十六进制。
    为了返回更有意义的数据,需要重写该方法。使用print或println打印引用或字符串拼接+引用都会自动调用该方法
Class<?> getClass() 用于返回调用对象执行时的Class实例,反射机制使用

包装类:

概念:

通常情况下基本数据类型的变量不是对象,为了满足万物皆对象的理念就需要对基本数据类型的变量进行打包封装处理变成对象,而负责将这些变量声明为成员变量进行对象化处理的相关类,叫做包装类。    

内置:

java.lang.Byte             byte
java.lang.Short         short
java.lang.Integer         int
java.lang.Long             long
java.lang.Float         float
java.lang.Double         double      // java.lang.Number类是个抽象类,是上述类的父类来描述所有类共有的成员。
java.lang.Boolean         boolean
java.lang.Character     char

Integer类:

基本概念:

java.lang.Integer类内部包装了一个int类型的变量作为成员变量,主要用于实现对int类型的包装并提供int类型到String类之间的转换等方法。

常用的常量:

public static final int MAX_VALUE 表示int类型可以描述的最大值,即2^31-1
public static final int MIN_VALUE 表示int类型可以描述的最小值,即-2^31
public static final int SIZE 表示int类型采用二进制补码形式的位数
public static final int BYTES 表示int类型所占的字节个数
public static final Class TYPE 表示int类型的Class实例

常用的方法:

Integer(int value) 根据参数指定的整数来构造对象(已过时)
Integer(String s) 根据参数指定的字符串来构造对象 (已过时)
int intValue() 获取调用对象中的整数值并返回
static Integer valueOf(int i) 根据参数指定整数值得到Integer类型对象,也可以传string
boolean equals(Object obj) 比较调用对象与参数指定的对象是否相等
String toString() 返回描述调用对象数值的字符串形式
static int parseInt(String s) 将字符串类型转换为int类型并返回
static String toString(int i) 获取参数指定整数的十进制字符串形式
static String toBinaryString(int i) 获取参数指定整数的二进制字符串形式
static String toHexString(int i) 获取参数指定整数的十六进制字符串形式
static String toOctalString(int i) 获取参数指定整数的八进制字符串形式

装箱和拆箱的概念:

在Java5发布之前使用包装类对象进行运算时,需要较为繁琐的“拆箱”和“装箱”操作;即运算前先将包装类对象拆分为基本类型数据,运算后再将结果封装成包装类对象。
从Java5开始增加了自动拆箱和自动装箱的功能。

自动装箱池:

Integer类的内部cache类提供了自动装箱池技术(vm cache,可以调优),将-128127之间的整数已经装箱完毕。
当通过Integer.valueOf使用该范围之间的整数时,无需装箱直接取用自动装箱池中的对象即可,从而提高效率。
    Interger it = 100;  注意会自动装箱,超过128的会导致it1==it2为false,但数值相等。小于128的内存地址会一致。
    int ib = it;
注意:
    new Integer(1)的方式会重新创建。

Double类:

概述:

java.lang.Double类型内部包装了一个double类型的变量作为成员变量,主要用于实现对double类型的包装并提供double类型到String类之间的转换等方法。

常用的方法:

double doubleValue() 获取调用对象中的浮点数据并返回
static Double valueOf(double d) 根据参数指定浮点数据得到Double类型对象
static double parseDouble(String s) 将字符串类型转换为double类型并返回
boolean isNaN() 判断调用对象的数值是否为非数字。0/0.0为nan

装箱和拆箱:

Double d = 3.14;
double d2 = d;
没有装箱池

Boolean类:

概述;
    java.lang.Boolean类型内部包装了一个boolean类型的变量作为成员变量,主要用于实现对boolean类型的包装并提供boolean类型到String类之间的转换等方法。
常用的方法:
    boolean booleanValue() 获取调用对象中的布尔数值并返回
    static Boolean valueOf(boolean b) 根据参数指定布尔数值得到Boolean类型对象,内部调用parseBoolean方法
    static boolean parseBoolean(String s) 将字符串类型转换为boolean类型并返回,内部用的是"true".equalsIgnoreCase(s),其他情况都返回false
装箱和拆箱:
    类似

Character类:

概述:

主要用于实现对char类型的包装并提供字符类别的判断和转换等方法。

常用的方法:

char charValue() 获取调用对象中的字符数据并返回
static Character valueOf(char c) 根据参数指定字符数据得到Character类型对象
boolean equals(Object obj) 比较调用对象与参数指定的对象是否相等
String toString() 返回描述调用对象数值的字符串形式
static boolean isUpperCase(char ch) 判断参数指定字符是否为大写字符
static boolean isLowerCase(char ch) 判断参数指定字符是否为小写字符
static boolean isDigit(char ch) 判断参数指定字符是否为数字字符
static char toUpperCase(char ch) 将参数指定的字符转换为大写字符
static char toLowerCase(char ch) 将参数指定的字符转换为小写字符

数学处理类:

math类:

java.lang.Math类主要用于提供执行数学运算的方法,如:对数,平方根。
常用的方法:
    static int max(int a, int b) 返回两个参数中的最大值
    static int min(int a, int b) 返回两个参数中的最小值
    static double pow(double a, double b) 返回第一个参数的幂
    static int abs(int a) 返回参数指定数值的绝对值
    static long round(double a) 返回参数四舍五入的结果
    static double sqrt(double a) 返回参数的平方根
    static double random() 返回0.0到1.0的随机数

BigDecimal类:

概念:

由于float类型和double类型在运算时可能会有误差,若希望实现精确运算则借助java.math.BigDecimal类型加以描述。
常用的方法
BigDecimal(String val) 根据参数指定的字符串来构造对象
BigDecimal add(BigDecimal augend) 用于实现加法运算
BigDecimal subtract(BigDecimal subtrahend) 用于实现减法运算
BigDecimal multiply(BigDecimal multiplicand) 用于实现乘法运算
BigDecimal divide(BigDecimal divisor) 用于实现除法运算,注意:必须要除尽。否则抛出ArithmeticException。
BigDecimal divide​(BigDecimal divisor, int roundingMode)常用RoundingMode.HALF_UP,需要import

BigInteger类:

概念:

若希望表示比long类型范围还大的整数数据,则需要借助java.math.BigInteger类型描述。

常用的方法:

BigInteger(String val) 根据参数指定的字符串来构造对象
BigInteger add(BigInteger val) 用于实现加法运算
BigInteger subtract(BigInteger val) 用于实现减法运算
BigInteger multiply(BigInteger val) 用于实现乘法运算
BigInteger divide(BigInteger val) 用于实现除法运算
BigInteger remainder(BigInteger val) 用于实现取余运算
BigInteger[] divideAndRemainder(BigInteger val) 用于实现取商和余数的运算

String类:

概念:

1.java.lang.String类用于描述字符串,Java程序中所有的字符串字面值都可以使用该类的对象加以描述,如:"abc"
    只有String可以new + 字面值
2.该类由final关键字修饰,表示该类不能被继承。
3.从jdk1.9开始该类的底层不使用char[]来存储数据,而是改成 byte[](1个字节)加上编码标记,从而节约了一些空间。
4.该类描述的字符串内容是个常量,不可更改(不可变性),因此可以被共享使用。
    如:
        String str1 = “abc”; - 其中"abc"这个字符串是个常量不可改变。在方法区(常量区)声明这个常量后,将地址返回给变量。
        str1 = “123”; - 将“123”字符串的地址赋值给变量str1。
                      - 改变str1的指向并没有改变指向的内容

G1的string去重优化:

概述:

针对堆(非常量池)相同的字符串对象进行去重。

实现:

当gc的时候,去重线程使用hashtable记录所有不重复的char数组,然后判断当前string是否重复,重复的情况下修改引用。

配置:

UseStringDeduplication
PrintStringDeduplicationStatistics
StringDeduplicationAgeThreshold    达到该年龄的对象才判断

字符串常量池:

概述:

由于String类型描述的字符串内容是常量不可改变,因此Java虚拟机将首次出现的字符串放入常量池中。
若后续代码中字面量语法出现了相同字符串内容则直接使用池中已有的字符串对象而无需申请内存及创建对象,从而提高了性能。

实现原理:

在HotSpot VM里实现的String Pool功能的是一个StringTable类,它是一个固定大小的HashTable,默认size为60013。所以String多了会导致链表过长,性能下降。-XX:StringTableSize
里面存的是字符串对象的引用,而不是字符串实例本身,具体的实例对象是在堆中开辟的一块空间存放的。
这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。
比如String a="ab",a保存的是字符串在常量池的变量引用,这个变量才是存的真正字符串实例的引用。
String a = new String(“ab”)中a保存的是字符串实例的引用。

内存位置:

1.7之前位于永久代,之后移动到堆区。

历史演进:

1.6以及之前每次会在常量池新建对象,而1.7之后保存的字符串字面量(堆上未有字符串对象的情况下使用新字面量)或者字符串对象引用(堆上已有字符串对象的情况下intern方法)。
理解思路2:
    字符串常量池中存的都是字符串对象的引用。字符串对象本身存在堆上。使用新字面量也是先创建堆上对象,然后常量池存着引用。(PrintStringTableStatistics尝试修改字符串长度发现常量池的统计信息没变)

特殊情况:

出现运算符:
"ab""a"+"b"是同一个,常量优化机制。常量是固定的。
如果是其中一个是变量,则不是同一个地址。
String s1 = "abc";        String s2 = "a" + "bc";     s1==s2;
String s11 = "a";        String s22 = "bc";      String s6 = s11 + s22;  s1!=s6;
通过new StringBuilder()方法生成字符串:
这样会生成新的字符串。

相等性验证:

声明两个相同的字符串,比较两个变量的内存地址即可。

配置:

-XX:StringTableSize
-XX:+PrintStringTableStatistics 打印统计信息

垃圾回收:

当内存不足后,会发生gc。

intern()方法:

首先检查字符串常量池中是否有该字符串字面量(equals方法判断),如果存在则返回池中字符串对象的引用,否则就将这个字符串对象的引用添加到字符串池中,然会返回这个字符串对象的引用。
一些内置的字符串:
    在加载初始类比如sun.misc.Version类的时候,会将一些字符串放入常量池。比如java。
放入字符串常量池的方式:
    1.使用字面量的任何时候:
        比如String a = "abc";
            new String("abc")
            new StringBuilder("abc")等等
    2.使用字符串对象.intern()方法
        String a = new String("1") + new String("2");
                    a.intern();  //调用之前常量池里面并没有12这个字符串
作用: 
    减小内存使用
示例: 
    String s = new StringBuilder("a").append("b").toString();
    s.intern()                  // 将s指向的堆上ab对象引用放入常量池
    String s1 = "ab";
    System.out.println(s==s1);   // true            

new String(“ab”)创建了多少个对象:

先在堆中字符串常量池中创建字符串字面量,然后再在堆中创建该字面量的字符串实例对象

String str = new String(“a”) + new String(“b”)创建了几个对象:

对象1new StringBuilder      // 每一个+符号的底层原理是创建new StringBuilder()对象,然后使用两次append方法
对象2new String(“a”)
对象3:字符串常量池a
对象4new String(“b”)
对象5:字符串常量池b
对象6new String("ab")

常用的构造方法:

String() 使用无参方式构造对象得到空字符序列
String(byte[] bytes, int offset, int length)使用bytes数组(ascii码)中下标从offset位置开始的length个字节来构造对象。原理:逐个翻译字节数字。
String(byte[] bytes) 使用bytes数组中的所有内容构造对象
String(char[] valueint offset, int count)使用value数组中下标从offset位置开始的count个字符来构造对象
String(char[] value) 使用value数组中的所有内容构造对象
String(String original)根据参数指定的字符串内容来构造对象,新创建对象为参数对象的副本。两个对象,一个在常量池,一个在堆区(new)。

常用的成员方法:

String toString() 返回字符串本身
byte[] getBytes() 将当前字符串内容转换为byte数组并返回        思路:拆分字符串为多个字符,然后转换为数字
char[] toCharArray() 用于将当前字符串内容转换为char数组并返回    

char charAt(int index) 方法charAt用于返回字符串指定位置的字符。
int length() 返回字符串字符序列的长度。
boolean isEmpty() 判断字符串是否为空,底层判断长度。

int compareTo(String anotherString) 用于比较调用对象和参数对象的大小关系。逐字符比较,返回的一个不同字符的ascii码数值相差。如果是长度不同,返回的一个长度减去第二个长度。
int compareToIgnoreCase(String str) 不考虑大小写,也就是'a'和'A'是相等的关系

String concat(String str) 用于实现字符串的拼接。更简单用+
boolean contains(CharSequence s) 用于判断当前字符串是否包含参数指定的内容,CharSequence是接口,String实现了该接口
String toLowerCase() 返回字符串的小写形式
String toUpperCase() 返回字符串的大写形式
String trim() 返回去掉前导和后继空白的字符串
boolean startsWith(String prefix) 判断字符串是否以参数字符串开头
boolean startsWith(String prefix, int toffset) 从指定位置开始是否以参数字符串开头
boolean endsWith(String suffix) 判断字符串是否以参数字符串结尾

boolean equals(Object anObject) 用于比较字符串内容是否相等并返回,逐字符比较。
int hashCode() 获取调用对象的哈希码值
boolean equalsIgnoreCase(String anotherString)用于比较字符串内容是否相等并返回,不考虑大小写,如:'A'和'a'是相等

int indexOf(int ch)用于返回当前字符串中参数ch指定的字符第一次出现的下标,失败返回-1
int indexOf(int ch, int fromIndex) 用于从fromIndex位置开始查找ch指定的字符
int indexOf(String str)在字符串中检索str返回其第一次出现的位置(第一个字符的下标),若找不到返回-1
int indexOf(String str, int fromIndex)表示从字符串的fromIndex位置开始检索str第一次出现的位置
int lastIndexOf(int ch) 用于返回参数ch指定的字符最后一次出现的下标
int lastIndexOf(int ch, int fromIndex)用于从fromIndex位置开始反向查找ch指定字符出现的下标
int lastIndexOf(String str) 返回str指定字符串最后一次出现的下标
int lastIndexOf(String str, int fromIndex)用于从fromIndex位置开始反向搜索的第一次出现的下标。

String substring(int beginIndex, int endIndex)返回字符串中从下标beginIndex(包括)开始到endIndex(不包括)结束的子字符串
String substring(int beginIndex)返回字符串中从下标beginIndex(包括)开始到字符串结尾的子字符串

boolean matches(String regex)判断当前正在调用的字符串是否匹配参数指定的正则表达式规则

String[] split(String regex)参数regex为正则表达式,以regex所表示的字符串为分隔符,将字符串拆分成字符串数组
String replace(char oldChar, char newChar)使用参数newChar替换此字符串中出现的所有参数oldChar
String replaceFirst(String regex,String replacement)替换此字符串匹配给定的正则表达式的第一个子字符串
String replaceAll(String regex,String replacement)将字符串中匹配正则表达式regex的字符串替换成replacement

字符串数字转整数:

int ib=0;
for(int i=0;i<str.length();i++){
    ib = ib*10+str2.charAt(i)-'0';
}
Interger.parseInt(string)也可以

整数转字符串:

String.valueof()
""+123

可变字符串类:

基本概念:

由于String类描述的字符串内容是个常量不可改变,当需要在Java代码中描述大量类似的字符串时,只能单独申请和存储,此时会造成内存空间的浪费。
为了解决上述问题,可以使用java.lang.StringBuilder类和java.lang.StringBuffer类来描述字符序列可以改变的字符串,如:"ab"
StringBuffer类是从jdk1.0开始存在,属于线程安全的类,因此效率比较低。
StringBuilder类是从jdk1.5开始存在,属于非线程安全的类,效率比较高。
作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder会改变其值。
为什么修改本身,还有返回本身?
    为了连续调用。
如何在StringBuilder和String之间互转?
    toString方法
    构造方法
性能排行?
    string < stringBuffer < stringBuilder

常用的构造方法:

StringBuilder() 使用无参方式构造对象,容量为16
StringBuilder(int capacity) 根据参数指定的容量来构造对象,容量为参数指定大小
StringBuilder(String str) 根据参数指定的字符串来构造对象,容量为:16+字符串长度

常用的成员方法:

int capacity() 用于返回调用对象的容量
int length() 用于返回字符串的长度,也就是字符的个数
StringBuilder insert(int offsetString str)插入字符串并返回调用对象的引用,就是自己。原值也改变了(不同于String)。
StringBuilder append(String str) 追加字符串,还可以是CharSequence、StringBuffer等形参。
StringBuilder deleteCharAt(int index)将当前字符串中下标为index位置的单个字符删除
StringBuilder delete(int startint end) 删除字符串
StringBuilder replace(int startint endString str)替换字符串
StringBuilder reverse() 字符串反转
还有charAt查找、indexOf、LastIndexOf、subString、setCharAt等。

容量的扩容算法:

底层采用byte[]来存放
ensureCapacityInternal(count + len);    确保容量往后满足。
        putStringAt(count, str);        放到count的位置。coutn表示当前char用了多少

private void ensureCapacityInternal(int minimumCapacity{
    int oldCapacity = value.length >> coder;        # value是底层byte数组。coder是编码相关,在utf16是1,在LATIN1是0
    if (minimumCapacity - oldCapacity > 0) {        # 容量不满足时,发生扩容
        value = Arrays.copyOf(value,
                newCapacity(minimumCapacity) << coder);     # 翻倍
    }
}
newCapacity方法具体算出新容量,utf16为最低长度的2倍或者2*旧容量+4,LATIN1是2*旧容量+2或者最低长度,具体配置通过COMPACT_STRINGS

时间:

System类:

基本概念:

Java.lang.System类中提供了一些有用的类字段和方法。

常用的方法:

static long currentTimeMillis()    返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差

Date类的概述:

基本概念:

java.util.Date类主要用于描述特定的瞬间,也就是年月日时分秒,可以精确到毫秒。

常用的方法:

Date() 使用无参的方式构造对象,也就是当前系统时间
Date(long date)根据参数指定毫秒数构造对象, 参数为距离197011000秒的毫秒数
long getTime() 获取调用对象距离197011000秒的毫秒数
void setTime(long time)设置调用对象为距离基准时间time毫秒的时间点

SimpleDateFormat类:

基本概念:

java.text.SimpleDateFormat类主要用于实现日期和文本之间的转换。

常用的方法:

SimpleDateFormat() 使用无参方式构造对象
SimpleDateFormat(String pattern)根据参数指定的模式来构造对象,模式主要有: y-年 M-月 d-日 H-时 m-分 s-秒
final String format(Date date)用于将日期类型转换为文本类型
Date parse(String source) 用于将文本类型转换为日期类型

Calendar类:

基本概念:

java.util.Calender类主要用于描述特定的瞬间,取代Date类中的过时方法实现全球化。
该类是个抽象类,因此不能实例化对象,其具体子类针对不同国家的日历系统,其中应用最广泛的是GregorianCalendar(格里高利历),对应世界上绝大多数国家/地区使用的标准日历系统。

常用的方法:

static Calendar getInstance()用于获取Calendar类型的引用
void set(int yearint monthint dateint hourOfDay, int minuteint second)用于设置年月日时分秒信息,month0开始,需要减1
void setTime(Date date)
Date getTime()用于将Calendar类型转换为Date类型
void set(int fieldint value) 设置指定字段的数值,Calendar.YEAR
void add(int fieldint amount) 向指定字段增加数值

注意:

Calendar本身是abstract(有构造方法但只能super调用),为啥可以返回类型的对象?
该静态方法返回的是子类。
多态的场景之三:返回类型形成多态

Java8日期类:

由来:

JDK 1.0中包含了一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用了。而Calendar并不比Date好多少。
它们面临的问题是:
    Date类中的年份是从1900开始的,而月份都从0开始。
    格式化只对Date类有用,对Calendar类则不能使用。
    非线程安全等。

概述:

Java 8通过发布新的Date-Time API来进一步加强对 日期与时间的处理。
java.time包:该包日期/时间API的基础包。
java.time.chrono包:该包提供对不同日历系统的访问。
java.time.format包:该包能够格式化和解析日期时间对象。
java.time.temporal包:该包包含底层框架和扩展特性。
java.time.zone包:该包支持不同时区以及相关规则的类。

LocalDate类:

基本概念:

java.time.LocalDate类主要用于描述年--日格式的日期信息,该类不表示时间和时区信息。

常用的方法:

static LocalDate now() 在默认时区中从系统时钟获取当前日期

LocalTime类:

基本概念:

java.time.LocalTime 类主要用于描述时间信息,可以描述时分秒以及纳秒。

常用的方法:

static LocalTime now() 从默认时区的系统时间中获取当前时间
static LocalTime now(ZoneId zone) 获取指定时区的当前时间

LocalDateTime类:

基本概念:

java.time.LocalDateTime类主要用于描述ISO-8601日历系统中没有时区的日期时间,如2007-12-03T10:15:30

常用的方法:

static LocalDateTime now()从默认时区的系统时间中获取当前日期时间
static LocalDateTime of(int yearint monthint dayOfMonth, int hourint minuteint second)根据参数指定的年月日时分秒信息来设置日期时间
static LocalDateTime LocalDateTime.ofInstant(Instant instant, ZoneId zone)根据Instant来设置日期时间
int getYear() 获取年份字段的数值
int getMonthValue() 获取112之间的月份字段
int getDayOfMonth() 获取日期字段
int getHour() 获取小时数
int getMinute() 获取分钟数
int getSecond() 获取秒数
LocalDateTime withYear(int year) 设置为参数指定的年,返回的是同类型新对象,和String类型相似
LocalDateTime withSecond(int second) 设置为参数指定的秒
LocalDateTime plusYears(long years) 加上参数指定的年
LocalDateTime plusSeconds(long seconds) 加上参数指定的秒
LocalDateTime minusYears(long years) 减去参数指定的年
LocalDateTime minusSeconds(long seconds) 减去参数指定的秒

Instant类:

基本概念:

java.time.Instant类主要用于描述瞬间的时间点信息。

常用的方法:

static Instant now() 从系统时钟上获取当前时间,utc时区
OffsetDateTime atOffset(ZoneOffset offset)将此瞬间与偏移量组合以创建偏移日期时间,ZoneOffset.ofHours(8)
static Instant ofEpochMilli(long epochMilli)根据参数指定的毫秒数来构造对象,参数为距离1970年1月1日0时0分0秒的毫秒数
long toEpochMilli() 获取距离1970年1月1日0时0分0秒的毫秒数

DateTimeFormatter类:

基本概念:

java.time.format.DateTimeFormatter类主要用于格式化和解析日期。

常用的方法:

static DateTimeFormatter ofPattern(String pattern) 根据参数指定的模式来获取对象
String format(TemporalAccessor temporal) 将参数指定日期时间转换为字符串,TemporalAccessor是接口,可以传Instant、LocalDateTime
TemporalAccessor parse(CharSequence text) 将参数指定字符串转换为日期时间

集合类库:

集合:

由来:

当需要在Java程序中记录单个数据内容时,则声明一个变量。
当需要在Java程序中记录多个类型相同的数据内容时,声明一个一维数组。
当需要在Java程序中记录多个类型不同的数据内容时,则创建一个对象。
当需要在Java程序中记录多个类型相同的对象数据时,创建一个对象数组。
当需要在Java程序中记录多个类型不同的对象数据时,则准备一个集合。

集合的框架结构:

Java中集合框架顶层框架是:java.util.Collection集合 和 java.util.Map集合。
其中Collection集合中存取元素的基本单位是:单个元素。
其中Map集合中存取元素的基本单位是:单对元素。

关系图:

image-20211220151633585image-20211220151633585
                     collection接口                                                     Map接口
           set接口                           List接口           queue接口                                
                    SortedSet接口                                                                                    SortedMap接口              
HashSet类 LinkedSet类 TreeSet类    ArrayList类 Vector类 LinkedList类  PriorityQueue类         HashMap类   HashTable类  TreeMap类
LinkedHashSet类                                                                            LinkedHashMap类

Collection集合:

基本概念:

java.util.Collection接口是List接口、Queue 接口以及Set接口的父接口,因此该接口里定义的方法
既可用于操作List集合,也可用于操作Queue集合和Set集合。

常用的方法:

boolean add(E e); 向集合中添加对象
boolean addAll(Collection<? extends E> c)用于将参数指定集合c中的所有元素添加到当前集合中
boolean contains(Object o)
; 判断是否包含指定对象,原理是Objects.equals(o, e),这句话相当于1.判断null2.判断自身,3.该类型的方法e.equals(o)
boolean containsAll(Collection<?> c) 判断是否包含参数指定的所有对象
boolean retainAll(Collection<?> c) 保留当前集合中存在且参数集合中存在的所有对象,交集。如果此集合因调用而更改就返回true。取代原集合。
boolean remove(Object o)
; 从集合中删除对象,成功返回true。多个只删除一个,原理是删除元素e ,使其为Objects.equals(o, e)
boolean removeAll(Collection<?> c) 从集合中删除参数指定的所有对象,改变了就返回true
void clear()
; 清空集合
int size(); 返回包含对象的个数
boolean isEmpty(); 判断是否为空,底层是判断size==0
boolean equals(Object o) 判断是否相等
int hashCode() 获取当前集合的哈希码值
Object[] toArray() 将集合转换为数组,Arrays.asList(object数组)可以逆转
Iterator iterator() 获取当前集合的迭代器

Iterator接口:

基本概念:

java.util.Iterator接口主要用于描述迭代器对象,可以遍历Collection集合中的所有元素。
java.util.Collection接口继承Iterator接口,因此所有实现Collection接口的实现类都可以使用该迭代器对象。

常用的方法:

boolean hasNext() 判断集合中是否有可以迭代/访问的元素,
next() 用于取出一个元素并指向下一个元素,位置会变化,不能多次遍历,可以再次调用iterator方法
void remove() 用于删除最后一个访问到的元素,这样可以防止超删。collection.remove(next获取到的obj)方法会发生并发修改异常。

应用:

官方的toString方法就是迭代拼接print

for each循环:

基本概念:

Java5推出了增强型for循环语句,可以应用数组和集合的遍历。是经典迭代的“简化版”。

底层原理:

迭代器

语法格式:

for(元素类型 变量名 : 数组/集合名称) {
    循环体;
}    

执行流程:

不断地从数组/集合中取出一个元素赋值给变量名并执行循环体,直到取完所有元素为止。

List集合:

基本概念:

java.util.List集合是Collection集合的子集合,该集合中允许有重复的元素并且有先后放入次序。
该集合的主要实现类有:ArrayList类、LinkedList类、Stack类、Vector类。
ArrayList类的底层是采用动态数组进行数据管理的,支持下标访问,增删元素不方便。
LinkedList类的底层是采用双向链表进行数据管理的,访问不方便(内存不连续),增删元素方便。
    可以认为ArrayList和LinkedList的方法在逻辑上完全一样,只是在性能上有一定的差别,ArrayList更适合于随机访问而LinkedList更适合于插入和删除;在性能要求不是特别苛刻的情形下可以忽略这个差别。
Stack类的底层是采用动态数组进行数据管理的,该类主要用于描述一种具有后进先出特征的数据结构,叫做栈(last in first out LIFO)。
Vector类的底层是采用动态数组进行数据管理的,该类与ArrayList类相比属于线程安全的类,效率比较低,以后开发中基本不用。扩容一般为2
    类似StringBuilder和StringBuffer的区别。

ArrayList类:

概念:
底层是数组,但可以动态修改(有个临时变量)
遍历中删除:
1.使用for index++的方式,list.remove(e)会更新索引下标。
    解决:index要不增。或者倒序遍历。
2.使用高级for循环(增强for循环)遍历删除/增加操作
    for (type e : es ){
        // 如果break不会报错
    }                   }
    可能会报ConcurrentModificationException异常。
源码分析扩容:
1.声明new ArrayList();构造方法里
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
其中transient Object[] elementData; 
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 是一个长度为0的数组
2.add方法添加元素时,

首先:

判断长度是否满了s == elementData.length。

满了的情况下发生扩容:

判断是否是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,如果是,先申请大小为10object数组
否则,取1.5倍*旧容量或旧容量+1的最大值。// addAll的情况下就不是1了,而是参数集合的size。
线程不安全:
现象:
1.判断容量时,size可能都判断满足,但写入就越界了。
    elementData[size++] = e 
2.有可能元素会被覆盖。
    elementData[size] = e;      //多个线程执行了这步。
    size = size + 1;
3.并发执行会提示java.util.ConcurrentModificationException
解决:

方案一:

JUC的CopyOnWriteArrayList,写时,加锁,复制原数组,写入,再将原容器的引用指向新的setArray(newElements),释放锁。
删除类似,先加锁再操作。
读操作get(int index)是非线程同步的,如果在for循环中使用下标的方式去读取数据,可能报错ArrayIndexOutOfBoundsException。推荐用迭代器的方式(原array的引用不会改变)。
适合读多写少的场景,缺点时是每次写时需要复制,容易GC。

方案二:

Vector类

方案三:

Collections.synchronizedList(new ArrayList<>()) 返回线程安全的list

LinkedList类:

常用方法:
void addFirst(E e)
void addLast(E e)
removeFirst()
removeLast()
源码分析add过程:
1.new时声明first和last变量
transient Node<E> first;
2.add方法会判断修改first和last,将旧的node.next指向新的last
常用的方法
    void add(int index, E element) 向集合中指定位置添加元素,与collect不同。
    boolean addAll(int index, Collection<? extends E> c) 向集合中添加所有元素
    E get(int index) 从集合中获取指定位置元素,返回类型为object,需要强转(但可能不是父子类型,发生类型转换异常)
    int indexOf(Object o) 查找参数指定的对象
    int lastIndexOf(Object o) 反向查找参数指定的对象
    E set(int index, E element) 修改指定位置的元素,返回原有元素
    E remove(int index) 删除指定位置的元素。注意for循环,size每次会减小(倒着删也可以);元素自动填充,后面的元素会补位。
        注意,本身还有remove(Object e)的方法,重载了。
    List subList(int fromIndex, int toIndex) 用于获取子List,共享内存空间。

Stack类:

常用方法:
boolean empty() 测试此堆栈是否为空。  
E peek() 查看此堆栈顶部的对象,而不将其从堆栈中删除。  
pop() 移除此堆栈顶部的对象,并将该对象作为此函数的值返回。  
push​(E item) 将项目推到此堆栈的顶部。  
int search​(Object o) 返回对象在此堆栈上的从1开始的位置。 

Queue集合:

基本概念:

java.util.Queue集合是Collection集合的子集合,与List集合属于平级关系。
该集合的主要用于描述具有先进先出特征的数据结构,叫做队列(first in first out FIFO)。
该集合的主要实现类是LinkedList类,因为该类在增删方面比较有优势。

常用的方法:

boolean offer(E e) 将一个对象添加至队尾,若添加成功则返回true
poll() 从队首删除并返回一个元素
E peek() 返回队首的元素(但并不删除)

Set集合:

基本概念:

java.util.Set集合是Collection集合的子集合,与List集合平级。
该集合中元素没有先后放入次序,且不允许重复。
该集合的主要实现类是:HashSet类 和 TreeSet类以及LinkedHashSet类。
其中HashSet类的底层是采用哈希表进行数据管理的。
其中TreeSet类的底层是采用红黑树进行数据管理的。
其中LinkedHashSet类与HashSet类的不同之处在于内部维护了一个双向链表,链表中记录了元素的迭代顺序,也就是元素插入集合中的先后顺序,因此便于迭代。

常用的方法:

参考Collection集合中的方法即可!

元素放入HashSet集合的原理:

使用元素调用hashCode方法获取对应的哈希码值,再由某种哈希算法计算出该元素在数组中的索引位置。
若该位置没有元素,则将该元素直接放入即可。
若该位置有元素,则使用新元素与已有元素依次比较哈希值,若哈希值不相同,则将该元素直接放入。
    若新元素与已有元素的哈希值相同,则使用新元素调用equals方法与已有元素依次比较。
    若相等则添加元素失败,否则将元素直接放入即可。

TreeSet集合:

概念:
由于TreeSet集合的底层采用红黑树进行数据的管理,当有新元素插入到TreeSet集合时,需要使用新元素与集合中已有的元素依次比较来确定新元素的合理位置。
元素默认从小到大排序了。
比较元素大小的规则有两种方式:
    使用元素的自然(自身)排序规则进行比较并排序,让元素类型实现java.lang.Comparable接口;(至少实现这个,不然报错ClassCastException
    使用比较器规则进行比较并排序,构造TreeSet集合时传入java.util.Comparator接口;
自然排序的规则比较单一,而比较器的规则比较多元化,而且比较器优先于自然排序;
自然排序:
负数表示新对象小于参数对象。0表示不放入。
implements Comparable<Person>{}

@Override
public int compareTo(Person o) {
    return this.getName().compareTo(o.getName());
}
比较器规则:
实现接口,选择匿名类(lambda表达式也可以)、实现接口的子类都可以。负数表示o1小于o2
Comparator<Person> comparator = new Comparator<Person>() {
    @Override
    public int compare(Person o1, Person o2) {
        return 0;
    }
};    

线程安全问题:

现象:
并发修改会报错
解决:
方案一:
CopyOnWriteArraySet,底层基于CopyOnWriteArrayList
方案二:
Collections.synchronizedSet(new HashSet<>())

Map集合:

基本概念:

java.util.Map<k,v style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">集合中存取元素的基本单位是:单对元素,其中类型参数如下:
K - 此映射所维护的键(Key)的类型,相当于目录。
V - 映射值(Value)的类型,相当于内容。
该集合中key是不允许重复的,而且一个key只能对应一个value。
该集合的主要实现类有:HashMap类、TreeMap类、LinkedHashMap类、Hashtable类、Properties类。
其中HashMap类的底层是采用哈希表进行数据管理的。
其中TreeMap类的底层是采用红黑树进行数据管理的。
其中LinkedHashMap类与HashMap类的不同之处在于内部维护了一个双向链表,链表中记录了元素的迭代顺序,也就是元素插入集合中的先后顺序,因此便于迭代。
其中Hashtable类是古老的Map实现类,与HashMap类相比属于线程安全的类,且不允许null作为key或者value的数值。
其中Properties类是Hashtable类的子类,该对象用于处理属性文件,key和value都是String类型的。
Map集合是面向查询优化的数据结构, 在大数据量情况下有着优良的查询性能。经常用于根据key检索value的业务场景。

常用的方法:

V put(K key, V value)将Key-Value对存入Map,若集合中已经包含该Key,则替换该Key所对应的Value,返回值为该Key原来所对应的Value,若没有则返回null
get(Object key) 返回与参数Key所对应的Value对象,如果不存在则返回null
boolean containsKey(Object key);判断集合中是否包含指定的Key
boolean containsValue(Object value);判断集合中是否包含指定的Value
V remove(Object key) 根据参数指定的key进行删除
Set keySet() 返回此映射中包含的键的Set视图
Collection values() 返回此映射中包含的值的Set视图
Set<Map.Entry<K,V>> entrySet() 返回此映射中包含的映射的Set视图,每个元素是Map.Entry<K,V>

HashMap集合:

java7和java8的变化:
链表数组 -> 链表超过一定长度后变为红黑树    TREEIFY_THRESHOLD
源码分析:
1.new声明,只是this.loadFactor = DEFAULT_LOAD_FACTOR。
2.put过程:
    先hash键,通过(h = key.hashCode()) ^ (h >>> 16)和(n - 1) & hash得到数组的索引位置i
    判断数组容量是否为0或者null,发生扩容。
    根据索引位置i找到元素:
        若该位置没有元素,则将该键值对直接放入即可。然后++modCount,超过thr时调用resize。
        若该位置有元素p,则使用key与已有元素依次比较key的哈希值
            若p.key调用equals方法与p相同,则将该元素的value直接赋值给该Node。
            若元素是TreeNode,调用putTreeVal方法
            若key不相同
                判断p.next是否为空,如果为空直接放到next,然后分析是否超过TREEIFY_THRESHOLD(默认是8),再决定是否建树treeifyBin
                                                    (里面会先判断链表长度是否超过MIN_TREEIFY_CAPACITY,64,如果没超过就resize)。
                判断当前链表的Node的key是否相等,相等就更新值。
3.扩容过程:
    一开始容量和thr都为0,赋值为DEFAULT_INITIAL_CAPACITY=16,thr为16*0.75=12。初始化一个数组Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    容量和thr不为0的情况下,newCap = oldCap << 1和newThr = oldThr << 1都翻倍了。
        遍历旧数组得到e,先置空旧位置
            看e是否有next,没有通过e.hash & (newCap - 1)放到新位置
            如果是TreeNode,((TreeNode<K,V>)e).split(this, newTab, j, oldCap)
            否则,e有next,桶的情况,顺序放到j或者j + oldCap的位置
        一次转移完旧数据。
遍历:
EntrySet,只需要取一次。
已知bug:
1.(JDK1.7 及更早版本)并发情况下扩容会出现死循环,因为要遍历链表将新元素加入新数组,遍历过程中不断将变量e.next指向newTable[i]。并发情况下有可能死循环。
    int i = indexFor(e.hash, newCapacity);
    e.next = newTable[i];   // 将当前元素指向上一个元素,相当于将原链表倒序了。
    newTable[i] = e;
    比如:
        线程A的e=1next=3。还没执行e.next时挂起,线程B的e=3,next=5,newTable[i]=1,执行e.next=newTable[i],即3.next=1,newTable[i]=3next=5
        这个时候线程A再执行,就会变为1.next=3,newTable[i]=1。形成死循环。
    解决:
        JDK1.8通过head和tail,每次tail.next=e来保证顺序遍历链表,修复了这个倒序bug。保证每个node永远执行下一个next,那么无论多线程执行在哪个环节,都不会出现问题。
        与运算解析:
            新数组下标:newTab[e.hash & (newCap - 1)] = e;  新容量为2倍,那么二进制最高位刚好多了一个1。hash在这个最高位有两种情况,1或者0
            if ((e.hash & oldCap) == 0) 判断hash后的值是否在最高位有1
            如果有1,说明新下标为j + oldCap。没有1,说明新下标还是j。
线程安全问题:
现象:
存在并发数据丢失的问题(比如应该hash冲突的,但现在只有一个key了)。
解决:

方案一:
ConcurrentHashMap:

1.7采用了分段锁技术,其中 Segment 继承于 ReentrantLock。能支持 N 个 Segment 这么多次数的并发。
1.8使用 如果为空CAS尝试插入(尝试需要依赖底层硬件来判断是否成功) + 不然synchronized。因为 jdk对 synchronized 优化是很到位的。

方案二:

Collections.synchronizedMap

Collections类:

基本概念:

java.util.Collections类主要提供了对集合操作或者返回集合的静态方法。

常用的方法:

static <T extends Object & Comparable<? super T>> T
max(Collection<? extends T> coll)根据元素的自然顺序返回给定集合的最大元素
static T max(Collection<? extends T> coll, Comparator<? super T> comp)根据指定比较器引发的顺序返回给定集合的最大元素
static <T extends Object & Comparable<?super T>> T min(Collection<? extends T> coll)根据元素的自然顺序返回给定集合的最小元素
static T min(Collection<? extends T> coll, Comparator<? super T> comp)根据指定比较器引发的顺序返回给定集合的最小元素
static void copy(List<? super T> dest, List<? extends T> src)将一个列表中的所有元素复制到另一个列表中,需要dest的size大于src的。
static void reverse(List<?> list) 反转指定列表中元素的顺序
static void shuffle(List<?> list) 使用默认的随机源随机置换指定的列表
static <T extends Comparable<? super T>> void sort(List list)根据其元素的自然顺序将指定列表按升序排序
static void sort(List list, Comparator<? super T> c)根据指定比较器指定的顺序对指定列表进行排序
static void swap(List<?> list, int i, int j) 交换指定列表中指定位置的元素

泛型机制:

基本概念:

只能引用类型
通常情况下集合中可以存放不同类型的对象,是因为将所有对象都看做Object类型放入的,因此从集合中取出元素时也是Object类型,
    为了表达该元素真实的数据类型,则需要强制类型转换,而强制类型转换可能会引发类型转换异常。
为了避免上述错误的发生,从Java5开始增加泛型机制,也就是在集合名称的右侧使用<数据类型>的方式来明确要求该集合中可以存放的元素类型,
    若放入其它类型的元素则编译报错。泛型只在编译时期有效,在运行时期不区分是什么类型。

本质:

参数化类型。
泛型参数T只存在于编译时,在编译后会被擦除。
泛型类型在逻辑上看以看成是多个不同的类型,实际上运行时,都是相同的底层基本类型。
在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。

底层原理:

泛型的本质就是参数化类型,也就是让数据类型作为参数传递,其中E相当于形式参数负责占位,而使用集合时<>中的数据类型相当于实际参数,用于给形式参数E进行初始化,
从而使得集合中所有的E被实际参数替换,由于实际参数可以传递各种各样广泛的数据类型,因此得名为泛型。

示例:

List<String> l = new LinkedList<String>();        
List<String> l = new LinkedList<>();            从java7开始的新特性:菱形特性,不用再写两遍

自定义泛型接口:

泛型接口和普通接口的区别就是后面添加了类型参数列表,可以有多个类型参数,如:<E, T.. >等。    

自定义泛型类:

泛型类和普通类的区别就是类名后面添加了类型参数列表,可以有多个类型参数,如:<E, T, .. >等。
实例化时没有指定类型时,默认是Object类型。
实例化泛型类时应该指定具体的数据类型,并且是引用数据类型而不是基本数据类型(基本类型可以用包装类)。
继承:
    父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型。
        如果不保留泛型和没有指定泛型,则父类的T为默认Object。
            A extends B
        如果不保留泛型但指定了泛型,则父类的T为指定类型
            A extends B<String>
        可以保留泛型
            A<T> extends B<T>   
    子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型。
            A<T,E> extends B<T>
示例: 
    public class Person<T> {
        private T gender;
    }

自定义泛型方法:

介绍:

泛型方法就是我们输入参数的时候,输入的是泛型参数,而不是具体的参数。我们在调用这个泛型方法的时需要对泛型参数进行实例化或者static

泛型方法的格式:

[访问权限] <泛型> 返回值类型 方法名([泛型标识 参数名称]) { 方法体; }
在静态方法中使用泛型参数的时候,需要我们把静态方法定义为泛型方法。
用到类的泛型参数的方法不能static

泛型方法的实现:

注意泛型方法只能实现,不能重写(接口没有这个概念),但可以重载。
必须实现<T extends father> void xxx的方法。
重载可以<T extends son> void xxx(T a),使用不用的泛型。(多态接口的变量不能使用重载的方法,因为接口没有定义,解决:泛型接口的继承,实现类可以指定具体子类)
触发:
    传入father对象时,触发第一个方法。
    传入son对象以及son的子类对象时,触发第二个方法,而且对象a的成员变量为son的值,而不是son的子类的值。
    传入otherSon对象时,仍然触发father的方法。

泛型方法的继承:

子类中可以对方法重写override,也可以重载overload。
重载示例:
    @Override
                  <T extends father> void print(T a)  // 重写,只能保持泛形一模一样。
    <T extends son> void print(T a)     // 重载,如果没有用到参数T,那么会提示错误,因为两个方法擦除泛型后都是同样的方法,both methods have same erasure
重载后触发:
    与普通方法类似,根据传参来确定。

泛型在继承上的体现:

如果B是A的一个子类或子接口,而G是具有泛型声明的类或接口,则G<B>并不是G<A>的子类型!
比如:StringObject的子类,但是List<String>并不是List<Object>的子类。但是是List的子类。

通配符的使用:

概述:

有时候我们希望传入的类型在一个指定的范围内,此时就可以使用泛型通配符了。
如:之前传入的类型要求为Integer类型,但是后来业务需要Integer的父类Number类也可以传入。

原理:

理解可以看泛型的本质

泛型中有三种通配符形式:

<?> 无限制通配符:表示我们可以传入任意类型的参数。        
    作为函数参数的多态传递,允许多个具体泛型传进来。
    因为既没有extends,也没有super,所以:
        不允许调用set(T)方法并传入引用(null除外);
        不允许调用T get()方法并获取T引用(只能获取Object引用)。
            不支持元素的添加操作。对比List不加泛型这个父类,可以添加元素,但相当于去掉了泛型。
            可以取元素,返回Object类型
    特点:
        Pair<?>是所有Pair<T>的超类,可以安全地向上转型。

<? extends E> 表示类型的上界是E,引用只能是E或者是E的子类。不支持添加操作(不确定具体子类型),支持获取,返回的是E类型
    List<? extends Fruit> flist = new ArrayList<Apple>();
    Fruit fruit = flist.get(0);
    用途:
        用于遍历list,里面包含不同类型的对象,返回接收的类型都是E。
        遍历的对象可以用于super存放

<? super E> 表示类型的下界是E,引用只能是E或者是E的父类。可以添加父类最多到E的类型(因为这些类型可以向上强转),只能获取返回Object类型。
    List<? super Fruit> flist = new ArrayList<Fruit>();
    flist.add(new Fruit());
    flist.add(new Apple());
    用途:
        能够存放父类最多到E的不同类型的对象。

搭配lambda表达式:

会提示Target method is generic

运行如何判断泛型参数E为某个类型:

如果入参类型不为E,目前没办法,可以先根据re的类型,用instanceof来判断
    // 通过反射获得泛型签名中的类型,貌似也只能获取继承于泛型类的当前类已确定的泛型
    interface A<TID{  
    }  
    class B implements A<StringInteger{  
    } 
如果输入参数类型为E,那么可以根据
    方法一:传目标的类型Type
        public <E> selectOneValue(String sql,Class<E> myClass) throws SQLException {}     
        int rs = conn.selectOneValue("select id from t_json",Integer.class)
        然后就可以用myClass.isAssignableFrom(String.class)来判断     // myClass为class java.lang.Integer
        //也可以将myClass通过new存入类变量中
            public class MyGenericClass<T{
                Class<T> t;
                public static <E> MyGenericClass<E> createMyGeneric(Class<E> t){    //通过该方法返回的instance包含类变量t为输入类型
                    return new MyGenericClass<E>(t);
                }
                public MyGenericClass(Class<T> t) {
                    this.t=t;
                }
                public void out() {
                    System.out.println(t);          //instance的其他方法即可使用变量t
                }
            }
    方法二:传目标的类型变量
        public <E> void doSomething(E a)
        a instanceof Integer[]
        a.getClass().isAssignableFrom(Integer[].class)

异常机制:

基本概念:

异常就是"不正常"的含义,在Java语言中主要指程序执行中发生的不正常情况。
java.lang.Throwable类是Java语言中错误(Error)和异常(Exception)的超类。
其中Error类主要用于描述Java虚拟机无法解决的严重错误,通常无法编码解决,如:JVM挂掉了等。
其中Exception类主要用于描述因编程错误或偶然外在因素导致的轻微错误,通常可以编码解决,如:0作为除数等。

异常的分类:

java.lang.Exception类是所有异常的超类(不能抛出),主要分为以下两种:

RuntimeException - 运行时异常,也叫作非检测性异常,运行后才可能抛出
IOException和其它异常 - 其它异常,也叫作检测性异常,所谓检测性异常就是指在编译阶段都能被编译器检测出来的异常。

其中RuntimeException类的主要子类:

ArithmeticException类 - 算术异常
ArrayIndexOutOfBoundsException类 - 数组下标越界异常
NullPointerException - 空指针异常
ClassCastException - 类型转换异常
NumberFormatException - 数字格式异常

注意:

当程序执行过程中发生异常但又没有手动处理时,则由Java虚拟机采用默认方式处理异常,而默认处理方式就是:打印异常的名称、异常发生的原因、异常发生的位置以及终止程序。

异常的捕获:

语法格式:

try {
    编写可能发生异常的代码;
}
catch(异常类型 引用变量名) {
    编写针对该类异常的处理代码;
}
...
finally {
    编写无论是否发生异常都要执行的代码;如果catch发生异常,也会执行。try或者catch的模块return前需要执行finally!使得finally可能修改返回值。
}

注意:

当需要编写多个catch分支时,切记小类型应该放在大类型的前面;
懒人的写法:
    catch(Exception e) {}
finally通常用于进行善后处理,如:关闭已经打开的文件等。    

异常的抛出:

基本概念:

在某些特殊情况下有些异常不能处理或者不便于处理时,就可以将该异常转移给该方法的调用者,这种方法就叫异常的抛出。
当方法执行时出现异常,则底层生成一个异常类对象抛出,此时异常代码后续的代码就不再执行。

语法格式:

访问权限 返回值类型 方法名称(形参列表) throws 异常类型1,异常类型2,...{ 方法体; }
如:
    public void show() throws IOException {}    

方法继承:

子类重写的方法不能抛出更大的异常、不能抛出平级不一样的异常,但可以抛出一样的异常、更小的异常以及不抛出异常。

规范:

若父类中被重写的方法没有抛出异常时,则子类中重写的方法只能进行异常的捕获处理。
若一个方法内部又以递进方式分别调用了好几个其它方法,则建议这些方法内可以使用抛出的方法处理到最后一层进行捕获方式处理。

自定义异常:

基本概念:

当需要在程序中表达年龄不合理的情况时,而Java官方又没有提供这种针对性的异常,此时就需要程序员自定义异常加以描述。

实现流程:

a.自定义xxxException异常类继承Exception类或者其子类。
b.提供两个版本的构造方法,一个是无参构造方法,另外一个是字符串作为参数的构造方法(字符串用于Exception父类)。

异常的产生:

throw new 异常类型(实参);
如:
    throw new AgeException("年龄不合理!!!");

异常的处理:

一个是向上throws,一个是就地处理(适合继承重写的方法,它的父类没有抛出异常)。

规范:

Java采用的异常处理机制是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护。

File类:

基本概念:

java.io.File类主要用于描述文件或目录路径的抽象表示信息,可以获取文件或目录的特征信息,如:大小等。

常用的方法:

File(String pathname) 根据参数指定的路径名来构造对象
File(String parent, String child) 根据参数指定的父路径和子路径信息构造对象
File(File parent, String child) 根据参数指定的父抽象路径和子路径信息构造对象
boolean exists() 测试此抽象路径名表示的文件或目录是否存在
String getName() 用于获取文件的名称
long length() 返回由此抽象路径名表示的文件的长度
long lastModified() 用于获取文件的最后一次修改时间,毫秒
String getAbsolutePath() 用于获取绝对路径信息
boolean delete() 用于删除文件,当删除目录时要求是空目录,不然返回true但还是删除失败
boolean createNewFile() 用于创建新的空文件
boolean mkdir() 用于创建目录
boolean mkdirs() 用于创建多级目录
File[] listFiles() 获取该目录下的所有内容,单层
boolean isFile() 判断是否为文件
boolean isDirectory() 判断是否为目录
File[] listFiles(FileFilter filter) 获取目录下满足筛选器的所有内容。new 一个匿名类,然后实现accept方法即可。

IO流:

概念:

IO就是Input和Output的简写,也就是输入和输出的含义。
IO流就是指读写数据时像流水一样从一端流到另外一端,因此得名为“流"。        

基本分类:

按照读写数据的基本单位不同,分为 字节流 和 字符流。
    其中字节流主要指以字节为单位进行数据读写的流,可以读写任意类型的文件。
    其中字符流主要指以字符(2个字节)为单位进行数据读写的流,只能读写文本文件。
按照读写数据的方向不同,分为 输入流 和 输出流(站在程序的角度)。
    其中输入流主要指从文件中读取数据内容输入到程序中,也就是读文件。
    其中输出流主要指将程序中的数据内容输出到文件中,也就是写文件。
按照流的角色不同分为节点流和处理流。
    其中节点流主要指直接和输入输出源对接的流。
    其中处理流主要指需要建立在节点流的基础之上的流。    

体系结构:

image-20211220152353255image-20211220152353255
                   IO流 
             字节流                                         字符流 
  InputStream                OutputStream                       Reader    Writer               -->      抽象类
FileInputStream            FileOutputStream                FileReader         FileWriter
BufferedInputStream     BufferedOutputStream           BufferedReader     BufferedWriter 
DataInputStream         DataOutputStream              InputStreamReader  OutputStreamReader         
ObjectInputStream        ObjectOutputStream                                  PrintWriter             
                           PrintStream                        

FileWriter类:

基本概念:

java.io.FileWriter类主要用于将文本内容写入到文本文件。

常用的方法:

FileWriter(String fileName) 根据参数指定的文件名构造对象,文件不存在会新建文件。会清空原内容。
FileWriter(String fileName, boolean append)以追加的方式根据参数指定的文件名来构造对象。文件不存在会新建文件。
void write(int c) 写入单个字符
void write(char[] cbuf, int off, int len)将指定字符数组中从数组偏移量off开始的len个字符写入此文件输出流
void write(char[] cbuf)将cbuf.length个字符从指定字符数组写入此文件输出流中
void flush() 刷新流
void close() 关闭流对象并释放有关的资源

FileReader类:

基本概念:

java.io.FileReader类主要用于从文本文件读取文本数据内容。

常用的方法:

FileReader(String fileName)根据参数指定的文件名构造对象
int read() 读取单个字符的数据并返回,返回-1表示读取到末尾
int read(char[] cbuf, int offset, int length)从输入流中将最多len个字符的数据读入一个字符数组下标offset开始的位置中,返回读取到的字符个数,返回-1表示读取到末尾
int read(char[] cbuf)从此输入流中将最多 cbuf.length 个字符的数据读入字符数组中,返回读取到的字符个数,返回-1表示读取到末尾
void close() 关闭流对象并释放有关的资源

FileOutputStream类:

基本概念:

java.io.FileOutputStream类主要用于将图像数据之类的原始字节流写入到输出流中。

常用的方法:

FileOutputStream(String name) 根据参数指定的文件名来构造对象
FileOutputStream(String name,boolean append)以追加的方式根据参数指定的文件名来构造对象
void write(int b) 将指定字节写入此文件输出流
void write(byte[] b, int off, int len)将指定字节数组中从偏移量off开始的len个字节写入此文件输出流
void write(byte[] b)将 b.length 个字节从指定字节数组写入此文件输出流中
void flush() 刷新此输出流并强制写出任何缓冲的输出字节
void close() 关闭流对象并释放有关的资源

FileInputStream类(重点):

基本概念:

java.io.FileInputStream类主要用于从输入流中以字节流的方式读取图像数据等。

常用的方法:

FileInputStream(String name)根据参数指定的文件路径名来构造对象
int read() 从输入流中读取单个字节的数据并返回,返回-1表示读取到末尾
int read(byte[] b, int off, int len)从此输入流中将最多len个字节的数据读入字节数组中,返回读取到的字节个数,返回-1表示读取到末尾
int read(byte[] b)从此输入流中将最多 b.length 个字节的数据读入字节数组中,返回读取到的字节个数,返回-1表示读取到末尾
void close() 关闭流对象并释放有关的资源
int available() 获取输入流所关联文件的大小

写文件:

write(int b)太慢,write(byte[] b)场景不通用,write(arr,0,res=fis.read(barr))适合将读出的长度写入。

BufferedOutputStream类(重点):

基本概念:

java.io.BufferedOutputStream类主要用于描述缓冲输出流,此时不用为写入的每个字节调用底层系统(减小io)。
处理流。
多了一层缓冲区,自己可以write单个字节(仍然慢),或者字节数组write(快很多)都可以。

常用的方法:

BufferedOutputStream(OutputStream out) 根据参数指定的引用来构造对象,默认8192
BufferedOutputStream(OutputStream outint size)根据参数指定的引用和缓冲区大小来构造对象
void write(int b) 写入单个字节
void write(byte[] b, int off, int len) 写入字节数组中的一部分数据
void write(byte[] b) 写入参数指定的整个字节数组
void flush() 刷新流
void close() 关闭流对象并释放有关的资源

BufferedInputStream类(重点):

基本概念:

java.io.BufferedInputStream类主要用于描述缓冲输入流。

常用的方法:

BufferedInputStream(InputStream in) 根据参数指定的引用构造对象,默认8192
BufferedInputStream(InputStream inint size) 根据参数指定的引用和缓冲区大小构造对象
int read() 读取单个字节
int read(byte[] b, int off, int len) 读取len个字节到数组中
int read(byte[] b) 读取b.length个字节
void close() 关闭流对象并释放有关的资源

BufferedWriter类(推荐):

基本概念:

java.io.BufferedWriter类主要用于写入单个字符、字符数组以及字符串到输出流中。

常用的方法:

BufferedWriter(Writer out) 根据参数指定的引用来构造对象
BufferedWriter(Writer outint sz) 根据参数指定的引用和缓冲区大小来构造对象
void write(int c) 写入单个字符到输出流中
void write(char[] cbuf, int off, int len)将字符数组cbuf中从下标off开始的len个字符写入输出流中
void write(char[] cbuf) 将字符串数组cbuf中所有内容写入输出流中
void write(String s, int off, int len) 将参数s中下标从off开始的len个字符写入输出流中
void write(String str) 将参数指定的字符串内容写入输出流中
void newLine() 用于写入行分隔符到输出流中
void flush() 刷新流
void close() 关闭流对象并释放有关的资源

BufferedReader类(推荐):

基本概念:

java.io.BufferedReader类用于从输入流中读取单个字符、字符数组以及字符串。

常用的方法:

BufferedReader(Reader in)根据参数指定的引用来构造对象。比如new BufferedReader(new InputStreamReader(System.in))输入。
BufferedReader(Reader inint sz)根据参数指定的引用和缓冲区大小来构造对象
int read()从输入流读取单个字符,读取到末尾则返回-1,否则返回实际读取到的字符内容
int read(char[] cbuf, int off, int len)从输入流中读取len个字符放入数组cbuf中下标从off开始的位置上,若读取到末尾则返回-1,否则返回实际读取到的字符个数
int read(char[] cbuf) 从输入流中读满整个数组cbuf
String readLine() 读取一行字符串并返回,返回null表示读取到末尾
void close() 关闭流对象并释放有关的资源

使用示例:

new BufferedReader(new InputStreamReader(new FileInputStream(filename),"iso-8859-1"));
    如果要确定编码,注意编码工具UniversalDetector.detectCharset(inputStream)会预读取一次,导致后面读取为null,推荐ReaderFactory.createBufferedReader()
new BufferedReader(new InputStreamReader(System.in))输入。
new BufferedReader(new FileReader(file.getName()))

BufferedReader in = new BufferedReader(new FileReader("test.log"));
            String str;
            while ((str = in.readLine()) != null) {
                System.out.println(str);
            }

PrintStream类:

基本概念:

java.io.PrintStream类主要用于更加方便地打印各种数据内容。

常用的方法:

PrintStream(OutputStream out) 根据参数指定的引用来构造对象
void print(String s) 用于将参数指定的字符串内容打印出来
void println(String x) 用于打印字符串后并终止该行
void flush() 刷新流
void close() 用于关闭输出流并释放有关的资源

PrintWriter类:

基本概念:

java.io.PrintWriter类主要用于将对象的格式化形式打印到文本输出流。

常用的方法:

PrintWriter(Writer out) 根据参数指定的引用来构造对象
void print(String s) 将参数指定的字符串内容打印出来
void println(String x) 打印字符串后并终止该行
void flush() 刷新流
void close() 关闭流对象并释放有关的资源

OutputStreamWriter类:

基本概念:

java.io.OutputStreamWriter类主要用于实现从字节流到字符流的转换。

常用的方法:

OutputStreamWriter(OutputStream out) 根据参数指定的引用来构造对象
OutputStreamWriter(OutputStream out, String charsetName)根据参数指定的引用和编码构造对象
void write(String str) 将参数指定的字符串写入
void flush() 刷新流
void close()用于关闭输出流并释放有关的资源

InputStreamReader类:

基本概念:

java.io.InputStreamReader类主要用于实现从字节流到字符流的转换。

常用的方法:

InputStreamReader(InputStream in) 根据参数指定的引用来构造对象
InputStreamReader(InputStream in, String charsetName)根据参数指定的引用和编码来构造对象
int read(char[] cbuf) 读取字符数据到参数指定的数组
void close() 用于关闭输出流并释放有关的资源

字符编码:

编码表的由来:

计算机只能识别二进制数据,早期就是电信号。为了方便计算机可以识别各个国家的文字,就需要
将各个国家的文字采用数字编号的方式进行描述并建立对应的关系表,该表就叫做编码表。

常见的编码表:

ASCII:美国标准信息交换码, 使用一个字节的低7位二位进制进行表示。
ISO8859-1:拉丁码表,欧洲码表,使用一个字节的8位二进制进行表示。
GB2312:中国的中文编码表,最多使用两个字节16位二进制为进行表示。
GBK:中国的中文编码表升级,融合了更多的中文文字符号,最多使用两个字节16位二进制位表示。(最高位1表示两个字节,0表示一个字节)
Unicode:国际标准码,融合了目前人类使用的所有字符,为每个字符分配唯一的字符码。所有的文字都用两个字节16位二进制位来表示。

编码的发展:

面向传输的众多 UTF(UCS Transfer Format)标准出现了,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。这是为传输而设计的编码并使编码无国界,这样就可以显示全世界上所有文化的字符了。
Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储成什么样的字节流,取决于字符编码方案。推荐的Unicode编码是UTF-8和UTF-16
UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。(0表示一个字节,110表示两个,1110表示三个)

DataOutputStream类:

基本概念:

java.io.DataOutputStream类主要用于以适当的方式将8种基本数据类型写入输出流中。

常用的方法:

DataOutputStream(OutputStream out)根据参数指定的引用构造对象 OutputStream类是个抽象类,实参需要传递子类对象
void writeInt(int v)用于将参数指定的整数一次性写入输出流,优先写入高字节,4个字节全写入,int写入后转换为char
void write(int v)两者不同,只写入一个字节。
void close() 用于关闭文件输出流并释放有关的资源

DataInputStream类:

基本概念:

java.io.DataInputStream类主要用于从输入流中读取基本数据类型的数据

常用的方法:

DataInputStream(InputStream in)根据参数指定的引用来构造对象 InputStream类是抽象类,实参需要传递子类对象
int readInt() 用于从输入流中一次性读取一个整数数据并返回,没有或者不对的话返回EOFException。
void close() 用于关闭文件输出流并释放有关的资源

ObjectOutputStream类(重点):

基本概念:

java.io.ObjectOutputStream类主要用于将一个对象的所有内容整体写入到输出流中。
只能将支持 java.io.Serializable 接口的对象写入流中。
类通过实现 java.io.Serializable 接口以启用其序列化功能。
所谓序列化主要指将一个对象需要存储的相关信息有效组织成字节序列的转化过程。

常用的方法:

ObjectOutputStream(OutputStream out) 根据参数指定的引用来构造对象
void writeObject(Object obj) 用于将参数指定的对象整体写入到输出流中
void close() 用于关闭输出流并释放有关的资源

示例:

public class User implements java.io.Serializable {
    @Serial
    private static final long serialVersionUID = -6302068608321621724L;
    private String name;
}

ObjectInputStream类(重点):

基本概念:

java.io.ObjectInputStream类主要用于从输入流中一次性将对象整体读取出来。
所谓反序列化主要指将有效组织的字节序列恢复为一个对象及相关信息的转化过程。

常用的方法:

ObjectInputStream(InputStream in)根据参数指定的引用来构造对象
Object readObject()主要用于从输入流中读取一个对象并返回 无法通过返回值来判断是否读取到文件的末尾
void close() 用于关闭输入流并释放有关的资源

序列化版本号:

序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,
如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)。

transient关键字:

transient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进去的。

经验的分享:

当希望将多个对象写入文件时,通常建议将多个对象放入一个集合中,然后将集合这个整体看做一个对象写入输出流中,此时只需要调用一次readObject方法就可以将整个集合的数据读取出来,
从而避免了通过返回值进行是否达到文件末尾的判断。

RandomAccessFile类:

基本概念:

java.io.RandomAccessFile类主要支持对随机访问文件的读写操作。

常用的方法:

RandomAccessFile(String name, String mode)根据参数指定的名称和模式构造对象
    r: 以只读方式打开
    rw:打开以便读取和写入
    rwd:打开以便读取和写入,同步文件内容的更新
    rws:打开以便读取和写入,同步文件内容和元数据的更新
int read() 读取单个字节的数据
void seek(long pos)用于设置从此文件的开头开始测量的文件指针偏移量,字节单位
void write(int b) 将参数指定的单个字节写入,如果存在会覆盖。
void close() 用于关闭流并释放有关的资源

多线程:

线程的创建:

Thread类的概念:

java.lang.Thread类代表线程,任何线程对象都是Thread类(子类)的实例。
Thread类是线程的模板,封装了复杂的线程开启等操作,封装了操作系统的差异性。

创建方式:

1.继承Thread类
概述:
自定义类继承Thread类并重写run方法,然后创建该类的对象调用start方法(run方法调用相当于普通成员方法调用,start才是新线程)。
示例:
test extends Thread
new test().start();
优缺点:
继承Thread类的方式代码简单,但是若该类继承Thread类后则无法继承其它类。
匿名内部类的方式:
new Thread() {
            @Override
            public void run() 
{
                System.out.println(Thread.currentThread().getName());
            }
        }.start();
lambda方式:
        new Thread(() -System.out.println(Thread.currentThread().getName())).start();
        同样
2.实现Runnable接口
概述:
自定义类实现Runnable接口并重写run方法,创建该类的对象作为实参来构造Thread类型的对象,然后使用Thread类型的对象调用start方法(target执行run方法)。
示例:
new Thread(new Runnable() {} ).start()
优缺点:
Runnable接口的方式代码复杂,但不影响该类继承其它类以及实现其它接口。
匿名内部类的方式:
new Thread(new Runnable() {        //区别在于此,有了实参
            @Override
            public void run() 
{
                System.out.println();
            }
        }) {}.start();        // 可以重新定义run方法。线程会执行该方法。
lambda方式:
new Thread(()->{System.out.println();}).start();        // {}可以省略。Runnable是函数式接口
3.实现Callable接口:
概述:
Java5开始新增加创建线程的第三种方式为实现java.util.concurrent.Callable接口。
重写call方法,然后将对象作为实参构造FutureTask类型的对象,再将future对象传入Thread类,调用start方法。
函数式接口
与Runnable接口的区别:
是否返回结果
是否抛出异常
实现的方法不同。
常用的方法:
V call() 计算结果并返回
FutureTask类:

概念:

java.util.concurrent.FutureTask类用于描述可取消的异步计算,该类提供了Future接口的基本实现,包括启动和取消计算、查询计算是否完成以及检索计算结果的方法,
也可以用于获取方法调用后的返回结果。
常用的方法
FutureTask(Callable callable) 根据参数指定的引用来创建一个未来任务
V get() 获取call方法计算的结果

使用:

FutureTask实现了Runnable接口,传递给Thread封装一下,然后start()
lambda表达式:
new Thread(new FutureTask(()-> {
            // logic
            return 1;
        })).start();

相关的方法:

Thread() 使用无参的方式构造对象,成员变量target为null,run方法会判断跳过执行。所以需要继承并重写run方法自定义功能。
Thread(String name) 根据参数指定的名称来构造对象
Thread(Runnable target)根据参数指定的引用来构造对象,其中Runnable是个接口类型
Thread(Runnable target,String name)根据参数指定引用和名称来构造对象
void run()若使用Runnable引用构造了线程对象,调用该方法时最终调用接口中的版本,若没有使用Runnable引用构造线程对象,调用该方法时则啥也不做
void start() 用于启动线程,Java虚拟机会自动调用该线程的run方法

线程的生命周期:

状态:

新建状态NEW - 使用new关键字创建之后进入的状态,此时线程并没有开始执行。
就绪状态READY - 调用start方法后进入的状态,此时线程还是没有开始执行。
运行状态RUNNING - 使用线程调度器调用该线程后进入的状态,此时线程开始执行,当线程的时间片执行完毕后任务没有完成时回到就绪状态。
消亡状态TERMINATED - 当线程的任务执行完成后进入的状态,此时线程已经终止。
阻塞状态BLOCKED - 当线程执行的过程中发生了阻塞事件进入的状态,如:lock方法。阻塞状态解除后进入就绪状态。
等待(WAITING) - 进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
超时等待(TIMED_WAITING) - 该状态不同于WAITING,它可以在指定的时间后自行返回。

流转图:

image-20211217173107288image-20211217173107288

线程的编号和名称:

long getId() 获取调用对象所表示线程的编号
String getName() 获取调用对象所表示线程的名称
void setName(String name) 设置/修改线程的名称为参数指定的数值
static Thread currentThread() 获取当前正在执行线程的引用,可以用于main方法和接口,获取当前主线程。

常用的方法:

static void yield()当前线程让出处理器(离开Running状态),使当前线程进入Runnable状态等待
static void sleep(times)使当前线程从 Running 放弃处理器进入Block状态, 休眠times毫秒, 再返回到Runnable。如果其他线程打断当前线程的Block(sleep), 就会发生InterruptedException。
    与Object类的wait方法的区别是不会释放锁。
void stop()停止线程,该方法已过时。
int getPriority() 获取线程的优先级
void setPriority(int newPriority)修改线程的优先级。优先级越高(数字越大)的线程不一定先执行,但该线程获取到时间片的机会会更多一些
void join() 主线程等待该线程start终止。默认主线程不会等待子线程start()执行完才执行下一步。
void join(long millis) 等待参数指定的毫秒数
boolean isDaemon() 用于判断是否为守护线程,默认不是
void setDaemon(boolean on)用于设置线程为守护线程

线程同步机制:

基本概念:

当多个线程同时访问同一种共享资源时,可能会造成数据的覆盖等不一致性问题,此时就需要对线程之间进行通信和协调,该机制就叫做线程的同步机制。
多个线程并发读写同一个临界资源时会发生线程并发安全问题。
异步操作:多线程并发的操作,各自独立运行。
同步操作:多线程串行的操作,先后执行的顺序。    

synchronized加锁:

概述:
在Java语言中使用synchronized关键字来实现同步/对象锁机制从而保证线程执行的原子性。
锁的范围:
同步代码块的方式:

格式如下:

synchronized(类类型的引用) {
    编写所有需要锁定的代码;
}
其中类类型的引用要求唯一,一般创建一个class demo{},然后实例化放到成员变量,传递给synchronized

注意:

对于继承Thread,如果实例化了两个子类对象,那么锁的只是本对象,因为成员变量独立,解决是加static关键字。
对于实现接口Runnable,传给Thread的对象如果是一个,那么可以实现锁
同步方法的方式:
直接使用synchronized关键字来修饰整个方法即可,锁的对象为当前实例,注意不同实例对象之间独立。
该方式等价于:
    synchronized(this) {            //this是当前调用对象。要注意是否this一致。
        整个方法体的代码 
    }
可以加static来锁类对象。对于重写方法比如run方法不方便加static,可以分离方法,然后调用传参。
    synchronized(类名.class)
静态方法的锁定:

1.当我们对一个静态方法加锁,如:

public synchronized static void xxx(){….}
那么该方法锁的对象是类对象。每个类都有唯一的一个类对象。获取类对象的方式:类名.class。
2.静态方法与非静态方法同时使用了synchronized后它们之间是非互斥关系的。原因在于:静态方法锁的是类对象而非静态方法锁的是当前方法所属对象。
注意事项:
1.使用synchronized保证线程同步应当注意:
多个需要同步的线程在访问同步块时,看到的应该是同一个锁对象引用。
2.在使用同步块时应当尽量减少同步范围以提高并发的执行效率。
底层实现:
通过显式(通过使用monitorenter和monitorexit指令)或隐式(通过方法调用和返回指令)的监视器monitor输入和退出来实现的。 
    显示就是使用monitorenter和monitorexit来控制同步代码块;隐式是修饰方法,在运行时常量池中通过ACC_SYNCHRONIZED来标志。
任何一个对象都有一个Monitor与之关联。
Monitor 是依靠底层操作系统的 Mutex Lock 来实现互斥的。
如果线程调用 wait() 方法,就会释放当前持有的 Mutex,并且该线程会进入 WaitSet 集合中,等待下一次被唤醒。如果当前线程顺利执行完方法,也将释放 Mutex
synchronized 和 volatile 修饰符的比较:
transient 序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。
volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
volatile 是变量修饰符;synchronized 可以修饰类、方法、变量。
volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
偏向锁、轻量级锁和重量级锁:
背景:
synchronized底层原理是互斥获取对象的monitor。
Monitor 依赖于底层操作系统的实现,存在用户态和内核态的转换,所以增加了性能开销。
同时线程等待阻塞,切换也需要性能消耗。
偏向锁:

概述:

偏向锁主要用来优化同一线程多次申请同一个锁的竞争。可能大部分时间一个锁都是被一个线程持有和竞争。假如一个锁被线程 A 持有,后释放;接下来又被线程 A 持有、释放……如果使用 monitor,则每次都会发生用户态和内核态的切换,性能低下。

流程:

1.当对象被当做同步锁并有一个线程抢到了锁时,锁标志位还是 01,“是否偏向锁”标志位设置为 1,并且记录抢到锁的线程 ID,表示进入偏向锁状态。
2.当一个线程再次访问这个同步代码或方法时,该线程只需去对象头的 Mark Word 判断是否有偏向锁指向它的 ID,无需再进入 Monitor 去竞争对象了。
3.一旦出现其它线程竞争锁资源,偏向锁就会被撤销。撤销时机是在全局安全点,暂停持有该锁的线程,同时坚持该线程是否还在执行该方法。是则升级锁;不是则被其它线程抢占。
    在高并发场景下,大量线程同时竞争同一个锁资源,偏向锁肯定会被撤销。开启偏向锁会带来更大的性能开销,所以可以优化关闭。
    -XX:-UseBiasedLocking //关闭偏向锁(默认打开)
    -XX:+UseHeavyMonitors  //设置重量级锁

适合场景:

同一线程多次申请同一个锁
轻量级锁:

概述:
如果另一线程竞争锁,由于这个锁已经是偏向锁,则判断对象头的 Mark Word 的线程 ID 不是自己的线程 ID,就会进行 CAS 操作获取锁:

成功,直接替换 Mark Word 中的线程 ID 为当前线程 ID,该锁更新为轻量级锁定状态。
失败,自旋失败一定次数后,偏向锁会升级为重量级锁。

适合场景:

不存在锁竞争的场景(交替)
或者存在锁竞争但不激烈,仍然可以用自旋锁优化

JUC:

概述:
java.util .concurrent工具包的简称。这是一个处理线程的工具包,JDK 1.5开始出现的。

Atomic包:

概述:
在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断。
java.util.concurrent.Atomic
分类:
原子类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference(适合自定义object
原子数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
原子属性更新器:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
解决 ABA 问题的原子类:
    AtomicMarkableReference(通过引入一个 boolean来反映中间有没有变过)
    AtomicStampedReference(通过引入一个 int 来累加来反映中间有没有变过)
AtomicStampedReference:
概述:
添加了Stamp字段来解决ABA问题
原理:
通过自定义类来封装值和stamp两个字段,作为一个对象val使用。
UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
原理:
主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,不断自旋。
CAS:
    概述: 
        compare and swap,比较并交换。
        比如atomicInteger.compareAndSet(expect,update)
    底层原理: 
        比如getAndIncrement方法,底层调用的是unsafe.getAndAddInt(this1,valueOffset,1)
            其中valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
        再底层:
            int var5;
            do {
                var5 = this.getIntVolatile(var1, var2);
            } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));     
            return var5;
            其中compareAndSwapInt是native方法,依赖于硬件功能实现了原子的操作,系统原语,执行过程不中断,可以理解为CPU的原子指令。
        汇编代码:
            UNSAFE_ENTRY(jboolen,Unsafe_CompareAndSwapInt(JNIEnv *env,jobject unsafe,jobject obj,jlong offset,jint e,jint x))
              UnsafeWrapper("Unsafe_CompareAndSwapInt")
              oop p = JNIHandles::resolve(obj);
              jint* addr = (jint *)index_oop_from_field_offset_long(p,offset);
              return (jint)(Atomic::compxchg(x,addr,e))==e;    // 实现比较替换,其中参数x是即将更新的值,参数e是原内存的值。
            UNSAFE_END
        Unsafe类:
            内部方法可以像C的指针一样直接操作内存。
            方法都是native修饰
    缺点:
        1.ABA问题。加version解决。
        2.自旋循环时间长,多次循环需要耗费CPU。
        3.只能针对某一个,而不是多个共享变量的,不能针对多个共享变量同时进行CAS操作,因为这多个变量之间是独立的,简单的把原子操作组合到一起,并不具备原子性。
            理解:即多层CAS的过程中,前面的CAS判断的变量可能发生了变化。

线程安全类和不安全类:

StringBuffer类是线程安全的类,但StringBuilder类不是线程安全的类。
Vector类和 Hashtable类是线程安全的类,但ArrayList类和HashMap类不是线程安全的类。(线程安全通过synchronized
Collections.synchronizedList() 和 Collections.synchronizedMap()等方法实现安全。

Locks(锁):

基本概念:
Java5开始提供了更强大的线程同步机制—使用显式定义的同步锁对象来实现。
java.util.concurrent.locks包.
Lock接口:
概述:
控制多个线程对共享资源进行访问的工具。
实现类:
ReentrantLock类,该类拥有与synchronized相同的并发性,在以后的线程安全控制中,经常使用ReentrantLock类显式加锁和释放锁。
    常用的方法:
        ReentrantLock() 使用无参方式构造对象
        void lock() 获取锁
        void unlock() 释放锁
        Condition newCondition() 返回condition对象,用于线程间同步。
    使用方式:
        lock.lock()         // 确保lock方法和try之间没有可能的异常发生
        try 
{
            ...
        } finally {
            lock.unlock()   // 这样unlock的时候不会对未加锁的对象解锁
        }
ReadWriteLock接口:
概述:
读写锁接口,读锁是共享锁。写锁是独占锁。
发生死锁的情况:
1.多个线程,都先获取读锁,然后再获取写锁,但是都在等待其他的读锁释放,因为读锁不能升级为写锁。
2.多把写锁被不同的线程先后获取,最后都在等待对方的写锁释放。
实现类:
ReentrantReadWriteLock,分别实现了ReadLock和WriteLock类,分别使用了共享锁和排它锁。
StampedLock类:
和ReadWriteLock相比,改进之处在于:读的过程中也允许获取写锁后写入!
示例:
    StampedLock stampedLock = new StampedLock();
    long stamp = stampedLock.tryOptimisticRead(); // 获得一个乐观读锁
    if (!stampedLock.validate(stamp)) { // 检查乐观读锁后是否有其他写锁发生
       //说明读取过程中有写入操作,因此可能读取到错误的数据
        stamp = stampedLock.readLock(); // 获取一个悲观读锁
        try {
            ......  //重新读取数据
        } finally {
            stampedLock.unlockRead(stamp); // 释放悲观读锁
        }
    }
底层原理:
背景:
ReentrantLock\CountDownLatch等内部都有Sync实例,类继承自AbstractQueuedSynchronizer。
概述:
AbstractQueuedSynchronizer类,抽象队列同步器,用来构建锁或者其他同步器组件的基础框架及整个JUC体系基石。
主要思想是将线程封装成队列的结点Node,通过FIFO队列来完成资源获取线程的排队工作,并通过一个volatile int类型变量state表示锁的持有状态,通过CAS完成state值的修改。
底层是CAS乐观锁(尝试需要依赖底层硬件来判断是否成功)+ 自旋 + LockSupport.park()阻塞。
lock流程:
执行tryAcquire(arg)方法,非公平锁的情况下,nonfairTryAcquire,
    如果state=0,则cas尝试将state设置为1,如果成功,将当前请求资源的线程Thread.currentThread()设置为有效的工作线程。
    否则,判断是否满足可重入条件
    都不满足的情况下,执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)。
        addWaiter(Node.EXCLUSIVE)负责添加结点node到队列,acquireQueued自旋判断是否应该park还是应该进行判断队首然后抢锁。
unlock流程:
执行tryRelease,判断state是否为1,将工作线程设置为null,并将state设置为0。
    如果成功,就将head的下一个节点调用LockSupport.unpark(s.thread)方法解除阻塞。
常见方法:
isHeldExclusively、tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared等
默认的实现都是CAS (compare and swap) + volatile 和 native 的逻辑
设计模式:
模板方法模式
自定义同步器时需要重写AQS提供的模板方法。
相关策略:
升降级策略:
只能从写锁降级为读锁(调用不会阻塞,但仍需主动释放写锁,否则其他获取不到)
    写锁,只有在当前没有任何其他线程持有读锁和写锁的时候,才能插队成功。
不能从读锁升级为写锁(调用写锁的lock方法会一直阻塞,在等待读锁释放)。
    读锁,在等待队列的头结点是尝试获取写锁的线程的时候,不允许读锁插队,否则可能会出现写锁等待太久饥饿的情况(读锁一直能申请,插队)。
两种资源共享方式:
Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:

公平锁:
概述:

按照线程在队列中的排队顺序,先到者先拿到锁。效率较低。
只要队列里有线程已经在排队,就不允许插队。每个线程获取锁时,会先查看锁的等待队列,为空or重入的情况下才能获取锁,否则FIFO进入队列。

实现原理:

tryAcquire判断当前线程是否队首结点。

示例:

private static ReentrantLock lock=new ReentrantLock(true);

非公平锁:

当线程要获取锁tryAcquire时,直接cas去抢锁,无需判断队列是否有其他线程等待,直接尝试抢锁。失败后才加入队列。
效率高。
可能导致线程饿死。
synchronized只支持非公平锁。

Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock。
不同同步器争用共享资源的方式不同,在实现时只需要实现共享资源 state 的获取与释放方式即可
可重入锁:
概述:
  1. 在线程获取锁的时候,如果已经获取锁的线程是当前线程的话则直接再次获取成功;
2. 由于锁会被获取n次,那么只有锁在被释放同样的n次之后,该锁才算是完全释放成功。
作用:
避免死锁。
synchronized实现原理:
每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。
当一个线程请求成功后(指向monitorenter且目标对象的计数器为0),JVM会记下持有锁的线程,并将计数器计为1。
此时其他线程请求该锁,则必须等待。而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增。
reentrantLock实现原理:
判断当前线程所在节点的前驱节点是否为head节点,state值+1
自旋锁:
概述:
cas + 循环 
示例:
lock方法 
    while(!atomicReference.compareAndSet(null,thread)) {}  //其他线程会阻塞等待直到null
unlock方法
    atomicReference.compareAndSet(Thread.currentThread(),null)
死锁:
现象:
多个线程相互等待对方锁的释放,若无外力干涉的情况下它们都将无法推进下去。
可能的原因:
1.系统资源不足。
2.进程运行推进顺序不合适。
3.资源分配不当。
查看命令:
jps查看进程号
jstack pid,会提示found n deadlock
与synchronized方式的比较:
1.Lock是显式锁,api接口,需要手动实现开启和关闭操作,而synchronized是隐式锁,关键字,执行锁定代码后自动释放。
2.Lock只有同步代码块方式的锁,而synchronized有同步代码块方式和同步方法两种锁。
3.Lock支持多种锁类型,比如是否公平,读写锁。而synchronized只有
4.两者都是可重入锁。synchronized通过获取自增,释放自减的方式实现重入。
5.lock可以让等待锁的线程响应中断,而synchronized不行,会一直等待下去。
    tryLock(time,unit)
    lockInterruptibly()放代码块中,然后调用interrupt()方法中断
6.reentrantLock用来实现分组唤醒指定线程,synchronized只能随机一个或者全部。
优点:
1.可以使锁更公平(按照申请顺序,和底层结构队列有关)
2.可以使线程在等待锁的时候响应中断(lockInterruptibly方法)
3.可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间(tryLock(long timeout, TimeUnit unit)方法)
4.可以在不同的范围,以不同的顺序获取和释放锁

集合的线程安全:

详见集合。

线程辅助类:

Semaphore信号量:

概述:
synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。
场景:
控制并发的数量;
多个共享资源的互斥使用。
示例:
Semaphore semaphore = new Semaphore(10);
semaphore.acquire();   获取一个许可。
semaphore.release();

CountDownLatch计数器:

概述:
CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,它可以让某一个线程等待直到count=0,再开始执行。    
控制多线程等待。比如等待指定数量的结果。
实现原理:
基于AQS的state,自己实现了tryAcquireShared方法来不断自旋获取。
示例:
CountDownLatch latch = new CountDownLatch(num);
latch.countDown();        // 减1,如果为0后,唤醒其他执行await方法的线程。
latch.await();            // 线程等待,直到count=0

CyclicBarrier循环栅栏:

概述:
CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。
主要应用场景和 CountDownLatch 类似。
CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。
CyclicBarrier默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await()方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
示例:
CyclicBarrier cyclicBarrier = new CyclicBarrier(3,new TourGuideTask()); // 传递给子线程。
cyclicBarrier.await();        // 子线程执行到这步后等待其他线程,到达的线程数量满足cnt后,执行TourGuideTask线程任务。然后子线程继续执行。
实现原理:
lock
每个线程lock.condition await阻塞等待
判断count=0后,执行设置的command,然后signalAll唤醒所有线程。
区别:
1.CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
2.调用CountDownLatch的countDown方法后,当前线程并不会阻塞,会继续往下执行;而调用CyclicBarrier的await方法,会阻塞当前线程,直到指定的线程全部都到达了指定点的时候,才能继续往下执行;
3.CountDownLatch方法比较少,操作比较简单,而CyclicBarrier提供的方法更多,比如能够通过getNumberWaiting(),isBroken()这些方法获取当前多个线程的状态,并且CyclicBarrier的构造方法可以传入barrierAction,指定当所有线程都到达时执行的业务功能;
4.CountDownLatch是不能复用的,而CyclicLatch是可以复用的。

线程交换数据:

Exchanger:

概述:
一个用于线程间协作的工具类,用于两个线程间交换数据。它提供了一个交换的同步点,在这个同步点两个线程能够交换数据。
交换数据是通过exchange方法来实现的,如果一个线程先执行exchange方法,那么它会同步等待另一个线程也执行exchange方法,这个时候两个线程就都达到了同步点,两个线程就可以交换数据。
示例:
final Exchanger exchanger = new Exchanger();
// 将Exchanger通过闭包或者参数传递给线程方法。
String data2 = (String) exchanger.exchange(data1);

ThreadLocal:

概述:

本质上是一个封装变量。
线程隔离。并发下安全。

示例:

ThreadLocal<Integer> num = new ThreadLocal<>();
Integer a = num.get();
num.set(1)

实现原理:

ThreadLocal封装类,封装变量,提供setget方法访问变量、设置值。
底层存放线程各自的ThreadLocalMap字典, key为当前threadLocal对象的WeakReference类对象(弱引用),value为对应的值。
开放地址法节省存储指针的空间
WeakReference弱引用,使得key可以进行回收。 

内存泄漏:

现象:
ThreadLocal 在没有外部强引用时,发生 GC 时会被回收,那么 ThreadLocalMap 中保存的 key 值就变成了 null,而 Entry 又被 threadLocalMap 对象引用,threadLocalMap 对象又被 Thread 对象所引用,那么当 Thread 一直不终结的话,value 对象就会一直存在于内存中,也就导致了内存泄漏,直至 Thread 被销毁后,才会被回收。
解决:
在使用完 ThreadLocal 变量后,需要我们手动 remove 掉,防止 ThreadLocalMap 中 Entry 一直保持对 value 的强引用,导致 value 不能被回收。
m.remove(this);

应用场景:

1.线程间数据隔离,各线程的 ThreadLocal 互不影响。
2.方便同一个线程使用某一对象,避免不必要的参数传递。
3.全链路追踪中的 traceId 或者流程引擎中上下文的传递一般采用 ThreadLocal
4.Spring 事务管理器采用了 ThreadLocal
5.Spring MVC 的 RequestContextHolder 的实现使用了 ThreadLocal

线程透传:

概述:
默认情况下,父子线程不能传递threadlocal变量
解决:
java.lang.InheritableThreadLocal可以传递。
其实现原理就是在创建子线程将父线程当前存在的本地线程变量拷贝到子线程的本地线程变量中。
问题:
一般情况下,都复用线程池里的线程,变量只会复制一次。
父子线程关系的ThreadLocal值传递已经没有意义。
解决:
TransmittableThreadLocal,可以把任务提交给线程池时的ThreadLocal值传递到任务执行时。
原理:
    任务运行时,重新获取了变量。
用法:
    1.TtlRunnable封装Runnable。
    2.ExecutorService封装:executorService = TtlExecutors.getTtlExecutorService(executorService)。
    3.使用Java Agent来修饰JDK线程池实现类。详见文档。

线程间通信:

Object类:

常用的方法:
void wait()用于使得线程进入等待状态,将当前线程放入wait set,直到其它线程调用notify()notifyAll()方法。自动释放synchronized对象锁。
    ObjectMonitor对象中有两个队列,都用来保存ObjectWaiter对象,分别是_WaitSet 和 _EntrySet。
void wait(long timeout)用于进入等待状态,直到其它线程调用方法或参数指定的毫秒数已经过去为止
void notify() 用于唤醒在此对象监视器上等待的单个线程。如果所有的线程都在此对象上(synchronized监控的对象)等待,那么只会选择一个线程,选择是任意性的,并在对实现做出决定时发生。
    只能被作为此对象监视器的所有者的线程来调用。一次只能有一个线程拥有对象的监视器。
void notifyAll() 用于唤醒等待的所有线程
缺点:
1.线程操作的wait()、notify()、notifyAll()方法只能在synchronized步控制方法或同步控制块内调用。
    如果在非同步控制方法或控制块里调用,程序能通过编译,但运行的时候,将得到 IllegalMonitorStateException 异常。
2.必须先wait再notify。
轮询间隔执行:
synchronized + wait实现自动释放,这样另一方可以获取到锁。
synchronized + notify可以让另一方获取到锁后,唤醒自己。
场景:
生产者消费者模型,控制wait和notify的执行速率即可。
搭配wait和notifyAll方法实现线程间通信:
synchronized void 方法 {
    if condition {
        this.wait()
    }
    //逻辑
    this.notifyAll() 
}
虚假唤醒问题:
示例:
if condition {
    this.wait()  //会被notifyAll无差别唤醒
}
// logic 
解决:
while condition {
    this.wait()
}

Condition接口:

概述:
synchronized可以配合wait和notify实现线程在条件不满足时等待,条件满足时唤醒。(其中等待的时候自动释放synchronized)
ReentrantLock使用Condition对象来实现wait和notify的功能。(也会释放lock,唤醒时会等候获取lock)
常用方法:
await()    与此 Condition 相关联的锁被原子释放,并且出于线程调度目的,当前线程被禁用,并且处于休眠状态
    1.将当前线程添加到条件队列尾部(等待队列),链表。
    2.fullyRelease释放当前线程获取的锁(通过操作 state 的值)
    3.调用 park 阻塞挂起当前线程
    4.唤醒,放到同步队列,抢锁
signal()
signalAll() 唤醒所有通过当前condition对象await方法睡眠的线程。
实现原理:
Condition 只是一个抽象类,它的主要实现逻辑是在 AQS 的内部类 ConditionObject 实现的。
与Object类wait/notify方法区别:
相同点:
底层原理比较类似,都是借助于队列。
都需要先获取锁。否则报错。
都是先wait/await,再notify/signal。
区别点:
ReentrantLock 类可以唤醒指定条件的线程,而 object 的唤醒是随机的。
实现定制化唤醒:
注意await方法和signalAll方法的condition对象对应的,定制唤醒指定的线程只需调用对应的condition对象即可。
比如ABC三组交替打印,利用三个condition+一个变量作为等待条件即可。

LockSupport:

概述:
用来创建锁和其他同步类的基本线程阻塞原语。
wait/notify的改良加强
park()和unpark()的作用分别是阻塞线程和解除阻塞线程。
方法:
LockSupport.park()
LockSupport.unpark(thread)
原理:
使用了一种名为Permit许可的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可,permit只有两个值1和0,默认0。
可以把许可看成一种0,1信号量,但与semaphore不同,permit的累加上限是0。
一开始调用LockSupport.park()方法(底层是UNSAFE.park方法),当前线程会阻塞,直到别的线程将当前线程的permit设置为1,park方法才会被唤醒。然后将permit再次设置为0并返回。
unpark(thread)方法会将threadpermit设置为1(注意多次调用不会累加,permit还是1),会自动唤醒thread线程。
线程的唤醒需要消费permit
优点:
不用锁
不用先阻塞再唤醒。可以先唤醒。

线程池:

概念:

线程池的概念:首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。
解决了创建线程的消耗。

优点:

线程复用;控制最大并发数量;管理线程。

相关类和方法:

Java5开始提供了线程池的相关类和接口:java.util.concurrent.Executors类和java.util.concurrent.ExecutorService接口。

Executors工具类:

概念:
工具类和线程池的工厂类,可以创建并返回不同类型的线程池,
常用方法:
static ExecutorService newCachedThreadPool()创建一个可根据需要创建新线程的线程池,可缓存线程池,此线程池不会对线程池大小做限制
    new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(),threadFactory)无缓冲队列,线程缓存一段时间
    适合执行很多短期异步的小程序或者负载较轻的服务器。
static ExecutorService newFixedThreadPool(int nThreads)创建一个可重用固定线程数的线程池,不会释放工作线程
    new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())
    适合一个个任务串行执行的场景
static ExecutorService newSingleThreadExecutor() 创建一个只有一个线程的线程池,不用每次重新创建
    new ThreadPoolExecutor(11,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory)
    适合执行长期的任务,性能较好。
ScheduledThreadPool:
    定时执行任务,通过DelayedWorkQueue来实现定时。
不成文强制规定:
Executors返回的线程池对象弊端如下:
FixedThreadPoolSingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
CacheThreadPoolScheduledThreadPool:允许创建线程数量为Integer.MAX_VALUE,可能会创建大量线程,从而导致CPU占用满,OOM
调用ThreadPoolExecutor的构造函数来自己根据业务场景创建线程池。
源码:
addWorker底层调用task.run()方法
worker执行完后再次调用addWorker进行复用

ExecutorService接口:

概念:
真正的线程池接口,主要实现类是ThreadPoolExecutor
构造方法:
ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
主要参数:
corePoolSize:核心线程数
    线程池的基本大小,即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。
maximumPoolSize:
    最大线程数,核心线程+非核心线程,非核心线程只会缓存keepAliveTime时间。
keepAliveTime:非核心线程闲置超时时间
queue
    任务队列,常见SynchronousQueue无缓冲等待队列,
    LinkedBlockingQueue无界缓存等待队列,当前执行的线程数量达到corePoolSize的数量时,剩余的元素会在阻塞队列里等待
    ArrayBlockingQueue有界缓存等待队列,防止资源耗尽,但是可能较难调整和控制。配合压测一般生产环境没有问题。
threadFactory:
    线程工厂,表示生成线程的线程工厂,用于创建线程。
RejectedExecutionHandler当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。
    AbortPolicy:
        直接抛出异常。这个策略默认情况下是,表示无法处理新任务时抛出异常,可以捕获异常之后来进行相应的处理。
    CallerRunsPolicy:
        当触发拒绝策略时,只要线程池没有关闭,就由提交任务的当前线程处理。当多次提交任务时,就会阻塞后续任务执行,性能和效率自然就慢了。
        适合场景:
            一般在不允许失败的、对性能要求不高、并发量较小的场景下使用
    DiscardOldestPolicy:
        丢弃队列里最久的一个任务,并再次尝试提交当前任务。
    DiscardPolicy:
        不处理,丢弃掉。
    自定义策略:
        根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务,存到数据库里,负载降低后启动线程取出到队列里。
        dubbo的线程拒绝策略:
            先输出warn日志,再打印栈信息,最后抛出异常。
        netty的线程池拒绝策略:
            类似CallerRunsPolicy,不同的是,新建了一个线程来处理该任务。
            缺点:
                创建线程时未做任何的判断约束,也就是说只要系统还有资源就会创建新的线程来处理,直到new不出新的线程了,才会抛创建线程失败的异常
        activeMq中的线程池拒绝策略:
            当触发拒绝策略时,在尝试一分钟的时间重新将任务塞进任务队列,当一分钟超时还没成功时,就抛出异常
        pinpoint中的线程池拒绝策略:
            定义了一个拒绝策略链,包装了一个拒绝策略列表,当触发拒绝策略时,会将策略链中的rejectedExecution依次执行一遍。
自定义示例:
ExecutorService executorService = 
new ThreadPoolExecutor(1,1,60L,TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2));
new ThreadPoolExecutor(1,1,1,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy())
常用方法:
void execute(Runnable command) 执行任务和命令,通常用于执行Runnable。对于异常信息会直接抛出去。并且线程退出。
Future submit(Callable task或Runnable task) 执行任务和命令,通常用于执行Runable和Callable,返回future对象(FutureTask接收这两类实参),可以获取结果或者异常信息
void shutdown() 启动有序关闭
工作流程:
先往core线程提交(这个时候才创建线程),再往阻塞队列提交,最后往非核心线程提交,非核心线程满了后执行handler
当一个线程完成任务后,超过keepAliveTime,线程池会判断:
    如果当前运行的线程数大于corePoolSize,会停掉该线程。
源码:
    if(task != null || (task = getTask()) != null)
作用:
    提交尽量避免创建非核心线程
    执行尽量先释放非核心线程
对于计算密集型和 IO 密集型的应用,你的线程池大小是怎么设置的?
公式1:
线程数 = CPU可用核心数/(1 - 阻塞系数)
计算密集型任务的阻塞系数为0,而IO密集型任务的阻塞系数则接近1(0.8-0.9左右)。
公式2:
Nthreads = Ncpu x Ucpu x (1 + W/C)
    W/C = 等待时间与计算时间的比率
    Ncpu = CPU的数量
                     Ucpu = 目标CPU的使用率, 0 <= Ucpu <= 1
经验值:
纯计算密集型任务,推荐N+1,+1是因为计算密集型的线程恰好在某时因为发生一个页错误或者因其他原因而暂停,刚好有一个“额外”的线程,可以确保在这种情况下CPU周期不会中断工作。
IO密集型任务,推荐2N,W/C的值就为1,那么对应的线程数确实为2N。
BlockingQueue接口:
阻塞队列概述:
一个队列, 通过一个共享的队列,可以使得数据由队列的一端输入,从另外一端输出
当队列是空的,从队列中获取元素的操作将会被阻塞
当队列是满的,从队列中添加元素的操作将会被阻塞
原理:
java8中,每个操作加ReentrantLock,满or空的时候通过Condition await方法等待。
类似思路:
    可以通过wait,notify,notifyAll,sychronized来实现。
实现类:
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。定长。
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。(大小默认值为maxValue)
    对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列。
    只有当其指定的延迟时间到了,才能够从队列中获取到该元素。
SynchronousQueue:一个不存储元素的阻塞队列。无缓冲,必须等待。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
常用方法:

抛出异常:

add     添加一个元素,返回true,如果队列满了会抛出异常。
remove  取出一个元素,如果队列为空抛出异常。
element 返回队首

返回布尔值:

offer    添加一个元素,如果能放进去则为true,否则false
poll    取出一个元素,为空返回null
peek 

阻塞:

put     队列满时会阻塞
take    队列为空时阻塞。

超时:

offer(object, timeout, unit
poll(timeout, unit)

异常的捕捉:

普通线程:

1.Thread.setDefaultUncaughtExceptionHandlerstart启动后,可以捕捉到异常。
2.如果没有任何处理,会直接抛出并结束程序。

线程池:

知识点:
submit方法默认不显示异常
execute方法默认显示
如何捕捉:
1.线程内try catch包起来,输出信息
2.线程池execute + Thread.setDefaultUncaughtExceptionHandler可以捕捉到异常。
3.线程池submit + try catch捕捉打印 + future.get阻塞获取返回值,如果是异常则catch能捕捉到栈信息。
    注意submit + thread.setDefaultUncaughtExceptionHandler不能捕捉到异常,因为run方法将异常放到了 setException(ex),并赋值给future对象。
    get方法本身是阻塞的
    线程池需要shutdown()才会退出。
4.实现Thread.UncaughtExceptionHandler接口,实现void uncaughtException(Thread t, Throwable e);方法,并将该handler传递给线程池的ThreadFactory
Executors.newFixedThreadPool(8,factory);
5.类似的,继承ThreadGroup,覆盖其uncaughtException方法。

Fork/Join框架:

概述:

Fork:把一个复杂任务进行分拆,大事化小 
Join:把分拆任务的结果进行合并

异步回调CompletableFuture:

背景:

Futrue在Java里面,通常用来表示一个异步任务的引用,比如我们将任务提交到线程池里面,然后我们会得到一个Futrue,在Future里面有isDone方法来 判断任务是否处理结束,还有get方法可以一直阻塞直到任务结束然后获取结果,但整体来说这种方式,还是同步的,因为需要客户端不断阻塞等待或者不断轮询才能知道任务是否完成。
future的缺点:
    (1)不支持手动完成
        我提交了一个任务,但是执行太慢了,我通过其他路径已经获取到了任务结果,现在没法把这个任务结果通知到正在执行的线程,所以必须主动取消或者一直等待它执行完成
    (2)不支持进一步的非阻塞调用
        通过Future的get方法会一直阻塞到任务完成,但是想在获取任务之后执行额外的任务,因为Future不支持回调函数,所以无法实现这个功能
    (3)不支持链式调用
        对于Future的执行结果,我们想继续传到下一个Future处理使用,从而形成一个链式的pipline调用,这在Future中是没法实现的。
    (4)不支持多个Future合并
        比如我们有10个Future并行执行,我们想在所有的Future运行完毕之后,执行某些函数,是没法通过Future实现的。
    (5)不支持异常处理
        Future的API没有任何的异常处理的api,所以在异步运行时,如果出了问题是不好定位的。

概述:

CompletableFuture在Java里面被用于异步编程,异步通常意味着非阻塞,可以使得我们的任务单独运行在与主线程分离的其他线程中,并且通过回调可以在主线程中得到异步任务的执行状态,是否完成,和是否异常等信息。
CompletableFuture实现了Future, CompletionStage接口,实现了Future接口就可以兼容现在有线程池框架,而CompletionStage接口才是异步编程的接口抽象,里面定义多种异步方法,通过这两者集合,从而打造出了强大的CompletableFuture类。

常用方法:

get              会阻塞等待结果返回。
complete           使future返回
runAsync           运行一个没有返回值的任务
supplyAsync     运行一个有返回值的任务
whenComplete    执行回调
whenCompleteAsync 异步
thenApply       运行另一个后续回调任务
thenAccept      任务完成后调用
exceptionally   发生异常时触发
handle             类似于thenAccept/thenRun方法,是最后一步的处理调用,但是同时可以处理异常
thenCompose     合并两个有依赖关系的CompletableFutures的执行结果
thenCombine        合并两个没有依赖关系的CompletableFutures任务

网络编程:

TCP:

编程模型:

服务器:
1)创建ServerSocket类型的对象并提供端口号;
2)等待客户端的连接请求,调用accept()方法;
3)使用输入输出流进行通信;
4)关闭Socket;
客户端:
(1)创建Socket类型的对象并提供服务器的IP地址和端口号;
(2)使用输入输出流进行通信;
(3)关闭Socket;

ServerSocket类:

概念:
java.net.ServerSocket类主要用于描述服务器套接字信息
常用方法:
ServerSocket(int port) 根据参数指定的端口号来构造对象
Socket accept() 侦听并接收到此套接字的连接请求
void close() 用于关闭套接字
示例:
ServerSocket socket = new ServerSocket(8080);
while (true) {
            Socket accept = socket.accept();
            sample();
        }

Socket类:

概念:
java.net.Socket类主要用于描述客户端套接字,是两台机器间通信的端点
常用方法:
Socket() 创建一个未连接的套接字,系统默认类型为SocketImpl。 可以用bind方法
Socket(String host, int port) 根据指定主机名和端口来构造对象
InputStream getInputStream() 用于获取当前套接字的输入流
OutputStream getOutputStream() 用于获取当前套接字的输出流
void close() 用于关闭套接字

输入输出:

输入:
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println("hello");
不限定PrintStream,可以Object类型和Data类型的stream都可以。
输出:
BufferedReader br = new BufferedReader(new InputStreamReader(accept.getInputStream()));

多线程:

封装类,继承Thread,重写run方法,构造函数接受socket套接字即可。
合成复用模型。

UDP:

编程模型:

接收方:
(1)创建DatagramSocket类型的对象并提供端口号;
(2)创建DatagramPacket类型的对象并提供缓冲区;
(3)通过Socket接收数据内容存放到Packet中,调用receive方法;
(4)关闭Socket;
发送方:
1)创建DatagramSocket类型的对象;
2)创建DatagramPacket类型的对象并提供接收方的通信地址;
3)通过Socket将Packet中的数据内容发送出去,调用send方法;
4)关闭Socket;

DatagramSocket类:

概念:
java.net.DatagramSocket类主要用于描述发送和接收数据报的套接字(邮局)。
常用方法:
DatagramSocket() 使用无参的方式构造对象,发送方
DatagramSocket(int port) 根据参数指定的端口号来构造对象,接收方
void receive(DatagramPacket p) 用于接收数据报存放到参数指定的位置
void send(DatagramPacket p) 用于将参数指定的数据报发送出去
void close() 关闭Socket并释放相关资源

DatagramPacket类:

概念:
java.net.DatagramPacket类主要用于描述数据报,数据报用来实现无连接包裹投递服务。
常用方法:
DatagramPacket(byte[] buf, int length)根据参数指定的数组来构造对象,用于接收长度为length的数据报,接收方
DatagramPacket(byte[] buf, int length,InetAddress address, int port)根据参数指定数组来构造对象,将数据报发送到指定地址和端口,发送方
InetAddress getAddress() 用于获取发送方或接收方的通信地址
int getPort() 用于获取发送方或接收方的端口号
int getLength() 用于获取发送数据或接收数据的长度,避免接受的数组长度超过

InetAddress类:

概念:
java.net.InetAddress类主要用于描述互联网通信地址信息。
常用方法:
static InetAddress getLocalHost() 用于获取当前主机的通信地址
static InetAddress getByName(String host) 根据参数指定的主机名获取通信地址

URL类:

基本概念:

java.net.URLUniform Resource Identifier)类主要用于表示统一的资源定位器

常用的方法:

URL(String spec) 根据参数指定的字符串信息构造对象
String getProtocol() 获取协议名称
String getHost() 获取主机名称
int getPort() 获取端口号
String getPath() 获取路径信息
String getFile() 获取文件名
URLConnection openConnection() 获取URLConnection类的实例

URLConnection类:

基本概念:

java.net.URLConnection类是个抽象类,该类表示应用程序和URL之间的通信链接的所有类的超类,主要实现类有支持HTTP特有功能的HttpURLConnection类。
HttpURLConnection类的常用方法
InputStream getInputStream() 获取输入流
void disconnect() 断开连接

反射机制:

基本概念:

通常情况下编写代码都是固定的,无论运行多少次执行的结果也是固定的,在某些特殊场合中编写代码时不确定要创建什么类型的对象,也不确定要调用什么样的方法,
    这些都希望通过运行时传递的参数来决定,该机制叫做动态编程技术,也就是反射机制。
通俗来说,反射机制就是用于动态创建对象并且动态调用方法的机制。
目前主流的框架底层都是采用反射机制实现的。    

Class类:

基本概念:

java.lang.Class类的实例可以用于描述Java应用程序中的类和接口,也就是一种数据类型。
该类没有公共构造方法,该类的实例由Java虚拟机和类加载器自动构造完成,本质上就是加载到内存中的运行时类。

获取Class对象的方式:

1.使用数据类型.class的方式可以获取对应类型的Class对象(掌握)。
    字符串表示形式是字符串“class”或“interface”,后跟一个空格,然后是getName返回的格式的类的完全限定名。如Class java.lang.String
    如果此类对象表示基本类型,则此方法返回基本类型的名称。 
    如果此类对象表示void,则此方法返回“void”。 
    如果此类对象表示数组类型,则此方法返回“class”,后跟getName 。
2.使用引用/对象.getClass()的方式可以获取对应类型的Class对象。注意基本数据类型不是对象,而方法只有类的对象才能调用。
3.使用包装类.TYPE的方式可以获取对应基本数据类型的Class对象。不是自身xxx.class
4.使用Class.forName(String xx)的方式来获取参数指定类型的Class对象(掌握)。如java.lang.String完整路径,不能获取基本数据类型
5.使用类加载器ClassLoader.loadClass(String)的方式获取指定类型的Class对象。可以通过getClassLoader获取。

常用的方法:

static Class<?> forName(String className) 用于获取参数指定类型对应的Class对象并返回。String可以动态输入(键盘输入,读取配置)。
T newInstance() 用于创建该Class对象所表示类的新实例,无参构造的方式。已过时。
Class<?>[] getInterfaces()获取该类实现的接口类

Constructor类:

基本概念:

java.lang.reflect.Constructor类主要用于描述获取到的构造方法信息

Class类的常用方法:

Constructor getConstructor(Class<?>... parameterTypes)用于获取此Class对象所表示类型中参数指定的公共构造方法,不传参时获取无参构造。传参String.class
Constructor<?>[] getConstructors()用于获取此Class对象所表示类型中所有的公共构造方法    
                 getDeclaredConstructors获取所有构造方法,包括公有、受保护、默认、私有

onstructor类的常用方法:

newInstance(Object... initargs)使用此Constructor对象描述的构造方法来构造Class对象代表类型的新实例
int getModifiers(String name) 获取方法的访问修饰符,整数类型
String getName(String name) 获取方法的名称
Class<?>[] getParameterTypes(String name)获取方法所有参数的类型

Field类:

基本概念:

java.lang.reflect.Field类主要用于描述获取到的单个成员变量信息。

Class类的常用方法:

Field getDeclaredField(String name)用于获取此Class对象所表示类中参数名字对应的单个成员变量信息
Field[] getDeclaredFields() 用于获取此Class对象所表示类中所有成员变量信息

Field类的常用方法:

Object get(Object obj) 获取参数对象obj中此Field对象所表示成员变量的数值
void set(Object obj, Object value)将参数对象obj中此Field对象表示成员变量的数值修改为参数value的数值
void setAccessible(boolean flag)当实参传递true时,则反射对象在使用时应该取消 Java 语言访问检查。
int getModifiers() 获取成员变量的访问修饰符
Class<?> getType() 获取成员变量的数据类型
String getName() 获取成员变量的名称

Method类:

基本概念:

java.lang.reflect.Method类主要用于描述获取到的单个成员方法信息。

Class类的常用方法:

Method getMethod(String nameClass<?>... parameterTypes)用于获取该Class对象表示类中名字为name参数为parameterTypes的指定公共成员方法
Method[] getMethods() 用于获取该Class对象表示类中所有公共成员方法

Method类的常用方法:

Object invoke(Object obj,Object... args)使用对象obj来调用此Method对象所表示的成员方法,实参传递args
int getModifiers() 获取方法的访问修饰符
Class<?> getReturnType() 获取方法的返回值类型
String getName() 获取方法的名称
Class<?>[] getParameterTypes() 获取方法所有参数的类型
Class<?>[] getExceptionTypes() 获取方法的异常信息

获取其它结构信息:

Package getPackage() 获取所在的包信息
Class<? super TgetSuperclass() 获取继承的父类信息
Class<?>[] getInterfaces() 获取实现的所有接口
Annotation[] getAnnotations() 获取注解信息
Type[] getGenericInterfaces() 获取泛型信息,接口的泛型。

修饰符:

Java语言提供了很多修饰符,主要分为以下两类:

- 访问修饰符
- 非访问修饰符

访问控制修饰符:

Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。
    1default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
    2private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
    3public : 对所有类可见。使用对象:类、接口、变量、方法 //类变量要想通过实例访问,得声明为public
    4protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。

默认情况:
    接口里的变量都隐式声明为 public static final,而接口里的方法默认情况下访问权限为 public

请注意以下方法继承的规则:
    父类中声明为 public 的方法在子类中也必须为 public
    父类中声明为 protected 的方法在子类中要么声明为 protected,要么声明为 public,不能声明为 private
    父类中声明为 private 的方法,不能够被继承。

非访问修饰符:

为了实现一些其他的功能,Java 也提供了许多非访问修饰符。
    1static 修饰符,用来修饰类方法和类变量。
        static变量:
            static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。 静态变量也被称为类变量。局部变量不能被声明为 static 变量。
        static方法:
            static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。
            对象可以访问静态方法。
    2final 修饰符,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
        final变量:
            表示"最后的、最终的"含义,变量一旦赋值后,不能被重新赋值。被 final 修饰的实例变量必须显式指定初始值。
            final 修饰符通常和 static 修饰符一起使用来创建类常量。
        final方法:
            可以被子类继承,但是不能被子类修改。声明 final 方法的主要目的是防止该方法的内容被修改。
    3abstract 修饰符,用来创建抽象类和抽象方法。
        抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。
        抽象方法是一种没有任何实现的方法,该方法的的具体实现由子类提供。

Optional类:

Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional 类的引入很好的解决空指针异常。
实例: 
    Optional<Integer> a = Optional.ofNullable(value1);
    Optional<Integer> b = Optional.of(value2);          //null会抛出一个 空指针异常
    Integer value1 = a.orElse(new Integer(0));          //可能为空,如果存在值,则返回该值,如果不存在值,则返回它收到的参数:
    Integer value2 = b.get();                           //值为 null 时也会抛出异常。为避免出现异常,您可选择首先检验其中是否存在值ifPresent()。
    return value1 + value2; 
方法:
    orElse()
    orElseGet()     //如果值存在,不会创建括号内对象
    orElseThrow()   //对象为空时,直接抛出一个异常
    flatMap
    filter
    or()            //如果对象包含一个值,则λ表达式不会执行
    ifPresentOrElse()   //Consumer  和 Runnable。如果对象包含一个值,则会执行 Consumer  动作;否则,会执行 Runnable  动作。

JDBC:

概念:

JDBC(Java Data Base Connectivity) 是 Java 访问数据库的标准规范.是一种用于执行SQL语句的Java API,可以为 多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。是Java访问数据库的标准规范.

原理:

JDBC是接口,驱动是接口的实现,没有驱动将无法完成数据库连接,从而不能操作数据库!每个数据库厂商都需 要提供自己的驱动,用来连接自己公司的数据库,也就是说驱动一般都由数据库生成厂商提供。
JDBC就是由sun公司定义的一套操作所有关系型数据库的规则(接口),而数据库厂商需要实现这套接口,提供数据库驱动jar包, 我们可以使用这套接口编程,真正执行的代码是对应驱动包中的实现类。

API使用:

1.注册驱动

JDBC规范定义驱动接口: java.sql.Driver
MySql驱动包提供了实现类: com.mysql.jdbc.Driver

Class.forName(数据库驱动实现类)加载和注册数据库驱动,数据库驱动由数据库厂商MySql提供 "com.mysql.jdbc.Driver"
源码里是执行static代码块,DriverManager.registerDriver(new Driver());
从 JDBC3 开始,目前已经普遍使用的版本。可以不用注册驱动而直接使用。 Class.forName 这句话可以省 略。

2.获得连接

Connection 接口,代表一个连接对象 ,具体的实现类由数据库的厂商实现 
Connection getConnection(String url, String user, String password) 通过连接字符串和用户名,密码来获取数据 库连接对象
示例:
    Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test""root""123");

3.获取语句执行平台

通过Connection 的 createStatement方法 获取sql语句执行对象
    Statement createStatement() 创建 SQL语句执行对象
Statement : 代表一条语句对象,用于发送 SQL 语句给服务器,用于执行静态 SQL 语句并返回它所生成结 果的对象。
    int executeUpdate(String sql); 执行insert update delete语句.返回int类型,代表受影响的行 数
    ResultSet executeQuery(String sql); 执行select语句, 返回ResultSet结果集对象

4.处理结果集

ResultSet接口 作用:封装数据库查询的结果集,对结果集进行遍历,取出每一条记录。
    boolean next()  可以循环获取
        1) 游标向下一行 2) 返回 boolean 类型,如果还有下一条记录,返回 true,否则返回 false
    xxx getxxx(String or int)  返回指定的结果
        1) 通过列名,参数是 String 类型。返回不同的类型 2) 通过列号,参数是整数,从 1 开始。返回不同的类型

5.释放资源

1) 需要释放的对象:ResultSet 结果集,Statement 语句,Connection 连接
2) 释放原则:先开的后关,后开的先关。ResultSet ==> Statement ==> Connection
3) 放在哪个代码块中:finally 块,与IO流一样,使用后的东西都需要关闭!关闭的顺序是先开后关, 先得到的后关闭,后得到的先关闭

封装工具类:

JDBCUtils
1. 定义字符串常量, 记录获取连接所需要的信息
2. 静态代码块, 随着类的加载而加载,Class.forName(DRIVERNAME);
3.获取连接的静态方法
    关闭资源的静态方法

预处理对象PreparedStatement:

接口介绍:

PreparedStatement 是 Statement 接口的子接口,继承于父接口中所有的方法。它是一个预编译的 SQL 语 句对象.
预编译: 是指SQL 语句被发送给db预编译,并存储在 PreparedStatement 对象中。然后可以使用此对象多次高效地执行 该语句。

特点:

因为有预先编译的功能,提高 SQL 的执行效率。
可以有效的防止 SQL 注入的问题,安全性更高

常用方法:

reparedStatement prepareStatement(String sql) 指定预编译的 SQL 语句,SQL 语句中使用占位符 ? 创建一个语句对象
int executeUpdate();        执行insert update delete语句.
ResultSet executeQuery();    执行select语句. 返回结果集对象 Resulet
void setDouble(int parameterIndex, double x)    将指定参数设置为给定 Java double 值。
void setObject(int parameterIndex, Object x)    使用给定对象设置指定参数的值。

事务相关API:

void setAutoCommit(boolean autoCommit)    参数是 true 或 false 如果设置为 false,表示关闭自动提交,相 当于开启事务
void commit()        提交事务
void rollback()        回滚事务

连接池:

介绍:

Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!
常见的连接池有 DBCP连接池, C3P0连接池, Druid连接池

DBCP连接池:

介绍:
DBCP也是一个开源的连接池,是Apache成员之一,在企业开发中也比较常见,tomcat内置的连接池。
在DBCP包中提供了DataSource接口的实现类,我们要用的具体的连接池 BasicDataSource 类
编写工具类:
1.定义常量 保存数据库连接的相关信息
2.创建连接池对象 (有DBCP提供的实现类)
public static BasicDataSource dataSource = new BasicDataSource();
3.使用静态代码块进行配置
static{
        dataSource.setDriverClassName(DRIVERNAME);
        dataSource.setUrl(URL);
        dataSource.setUsername(USERNAME);
        dataSource.setPassword(PASSWORD);
}
4.获取连接的方法
connection connection = dataSource.getConnection();
接下来获取statement。
5.释放资源方法
con.close();归还连接
常见配置项:
driverClassName 数据库驱动名称
url数据库地址
username
password
maxActive最大连接数量
maxIdle最大空闲连接
minIdle最小空闲连接
initialSize初始化连接

C3P0连接池:

介绍:
C3P0是一个开源的JDBC连接池,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate、Spring等。
配置:
导入配置文件 c3p0-config.xml
c3p0-config.xml 文件名不可更改 
直接放到src下,也可以放到到资源文件夹中,在项目下创建一个resource文件夹(专门存放资源文件),将文件放在resource目录下即可,创建连接池对象的时候会去加载这个配置文件
编写C3P0工具类:
1.创建连接池对象 C3P0对DataSource接口的实现类
C3P0提供的核心工具类, ComboPooledDataSource , 如果想使用连接池,就必须创建该类的对象
new ComboPooledDataSource(); 使用 默认配置
new ComboPooledDataSource("mysql"); 使用命名配置
2.获取连接的方法
dataSource.getConnection();
常见配置:
user 用户名 
password 
driverClass 
jdbcUrl
initialPoolSize 
maxPoolSize 
minPoolSize 
maxIdleTime

Druid连接池:

介绍:
Druid(德鲁伊)是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行 情况。
导入配置文件:
是properties形式的
可以叫任意名称,可以放在任意目录下,我们统一放到 resources资源目录
编写Druid工具类:
1.创建属性集对象
Properties p = new Properties();
2.加载配置文件 Druid 连接池不能够主动加载配置文件 ,需要指定文件
InputStream inputStream =DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties");
Druid 连接池不能够主动加载配置文件 ,需要指定文件
3.使用Properties对象的 load方法 从字节流中读取配置信息
p.load(inputStream);
4.通过工厂类获取连接池对象
dataSource = DruidDataSourceFactory.createDataSource(p);
5.获取连接
dataSource.getConnection();

DBUtils工具类:

简介:
使用JDBC我们发现冗余的代码太多了,为了简化开发 我们选择使用 DbUtils
Commons DbUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能。
使用方式:
DBUtils就是JDBC的简化开发工具包。需要项目导入 commons-dbutils-1.6.jar
核心功能介绍:
1. QueryRunner 中提供对sql语句操作的API.
2. ResultSetHandler接口,用于定义select操作后,怎样封装结果集. 
3. DbUtils类,他就是一个工具类,定义了关闭资源与事务处理相关方法.
表和类之间的关系:
整个表可以看做是一个类
表中的一行记录,对应一个类的实例(对象)
表中的一列,对应类中的一个成员属性
JavaBean组件:
JavaBean 就是一个类, 开发中通常用于封装数据,有以下特点
    1. 需要实现 序列化接口, Serializable (暂时可以省略) 
    2. 提供私有字段: private 类型 变量名;
    3. 提供 getter 和 setter
    4. 提供 空参构造
我们可以创建一个entity包,专门用来存放 JavaBean类
QueryRunner核心类:
构造方法:
QueryRunner()    DbUtils.closeQuietly(con);con关闭可以用这个方法,因为没有statement了,所以不用关闭。
QueryRunner(DataSource ds) ,提供数据源(连接池),DBUtils底层自动维护连接connection(创建关闭),如QueryRunner qr2 = new QueryRunner(DruidUtils.getDataSource());
常用方法:
update([Connection conn,] String sql, Object... params) ,用来完成表数据的增加、删除、更新操 作
query([Connection conn,] String sql, ResultSetHandler<T> rsh, Object... params) ,用来完成表 数据的查询操作
ResultSetHandler接口:
简介:
ResultSetHandler可以对查询出来的ResultSet结果集进行处理,达到一些业务上的需求。
常见实现类:
ArrayHandler将结果集中的第一条记录封装到一个Object[]数组中,数组中的每一个元素就是这 条记录中的每一个字段的值。
ArrayListHandler将结果集中的每一条记录都封装到一个Object[]数组中,将这些数组在封装到List集 合中。
BeanHandler将结果集中第一条记录封装到一个指定的javaBean中。如参数new BeanHandler<Employee>(Employee.class)通过反射机制一一对应属性创建对象
BeanListHandler将结果集中每一条记录封装到指定的javaBean中,再将这些javaBean在封装到List 集合中
ColumnListHandler将结果集中指定的列的字段值,封装到一个List集合中
KeyedHandler将结果集中每一条记录封装到Map<String,Object>,在将这个map集合做为另一个 Map的value,另一个Map集合的key是指定的字段的值。
MapHandler将结果集中第一条记录封装到了Map<String,Object>集合中,key就是字段名称, value就是字段值
MapListHandler将结果集中每一条记录封装到了Map<String,Object>集合中,key就是字段名称, value就是字段值,在将这些Map封装到List集合中。
ScalarHandler它是用于封装单个数据。例如 select count(*) from 表操作。

数据库批处理:

配置:

mysql 批处理是默认关闭的,所以需要加一个参数才打开mysql 数据库批处理,在url中添加 rewriteBatchedStatements=true

实现:

Statement和PreparedStatement都支持批处理操作

PreparedStatement的批处理方式:

void addBatch()将给定的 SQL 命令添加到此 Statement 对象的当前命令列表中。 通过调用方法 executeBatch 可以批量执行此列表中的命令。
int[] executeBatch()每次提交一批命令到数据库中执行,如果所有的命令都成功执行了,那么返回一个数组,这个数组是说明每条命令所影响的行数
示例:
    ps.setString(1,"小强"+i)
;
    ps.addBatch();
    ps.executeBatch();

MySql元数据:

介绍:

除了表之外的数据都是元数据,可以分为三类
查询结果信息: UPDATE 或 DELETE语句 受影响的记录数。 
数据库和数据表的信息: 包含了数据库及数据表的结构信息。 
MySQL服务器信息: 包含了数据库服务器的当前状态,版本号等。

常用命令:

select version(); 获取mysql服务器的版本信息
show status; 查看服务器的状态信息
show columns from table_name; 显示表的字段信息等,和desc table_name一样
show index from table_name; 显示数据表的详细索引信息,包括PRIMARY KEY(主键)
show databases:列出所有数据库 
show tables : 显示当前数据库的所有表 
select database(): 获取当前的数据库名

使用JDBC 获取元数据:

通过JDBC 也可以获取到元数据,比如数据库的相关信息,或者当我们使用程序查询一个不熟悉的表时, 我们可以通过获取元素据信息,了解表中有多少个字段,字段的名称 和 字段的类型.

常用类介绍:

DatabaseMetaData描述数据库的元数据对象
ResultSetMetaData描述结果集的元数据对象

获取元数据对象的方法:

xxx.getMetaData ()
    connection 连接对象, 调用 getMetaData () 方法,获取的是DatabaseMetaData 数据库元数据对象
    PrepareStatement 预处理对象调用 getMetaData () , 获取的是ResultSetMetaData , 结果集元数据对象

DatabaseMetaData的常用方法:

getURL() : 获取数据库的URL
getUserName(): 获取当前数据库的用户名 
getDatabaseProductName(): 获取数据库的产品名称 
getDatabaseProductVersion(): 获取数据的版本号 
getDriverName(): 返回驱动程序的名称
isReadOnly(): 判断数据库是否只允许只读 true 代表只读

ResultSetMetaData的常用方法:

getColumnCount() : 当前结果集共有多少列
getColumnName(int i) : 获取指定列号的列名, 参数是整数 从1开始 
getColumnTypeName(int i): 获取指定列号列的类型, 参数是整数 从1开始

XML:

作用:

1.存储数据。通常,我们在数据库中存储数据。不过,如果希望数据的可移植性更强,我们可以 把数据存储 XML 文件中

2.配置文件。作为各种技术框架的配置文件使用 (最多)

3.传输格式。客户端可以使用XML格式向服务器端发送数据,服务器接收到xml格式数据,进行解析

语法:

文档声明必须为结束;
文档声明必写在第一行;

XML约束:

XML技术里,可以编写一个文档来约束一个XML文档的书写规范,这称之为XML约束。
常见的xml约束:
    DTD
    Schema
DTD约束
    介绍:
        TD(Document Type Definition),文档类型定义,用来约束XML文档。规定XML文档中元素的名称,子元素的名称及顺序,元素的属性等。
        常情况我们都是通过框架提供的DTD约束文档,编写对应的XML文档。常见框架使用DTD约束有: Struts2hibernate等。
    编写DTD
        创建约束文件 student.dtd
    引入DTD
        引入dtd文档到xml文档中,两种方式 
            内部dtd:将约束规则定义在xml文档中 
            外部dtd:将约束的规则定义在外部的dtd文件中
Schema约束
    介绍:
        1. Schema是新的XML文档约束, 比DTD强大很多,是DTD 替代者;
        2. Schema本身也是XML文档,但Schema文档的扩展名为xsd,而不是xml。 
        3. Schema 功能更强大,内置多种简单和复杂的数据类型
        4. Schema 支持命名空间 (一个XML中可以引入多个约束文档)
    XML引入Schema约束

XML 解析:

开发中比较常见的解析方式有两种,如下:

DOM:
要求解析器把整个XML文档装载到内存,并解析成一个Document对象。
                 优点:元素与元素之间保留结构关系,故可以进行增删改查操作。
缺点:XML文档过大,可能出现内存溢出显现。 
SAX:是一种速度更快,更有效的方法。它逐行扫描文档,一边扫描一边解析。并以事件驱动的方 式进行具体解析,每执行一行,都将触发对应的事件。(了解)
优点:占用内存少 处理速度快,可以处理大文件 
缺点:只能读,逐行后将释放资源。

常见的解析器:

就是根据不同的解析方式提供的具体实现。有的解析器操作过于繁琐,为了方便开发人员,有提供易于操作的解析开发包
JAXP:sun公司提供的解析器,支持DOMSAX两种思想
DOM4J:一款非常优秀的解析器 , Dom4j是一个易用的、开源的库,用于XMLXPathXSLT。 它应用于Java平台,采用了Java集合框架并完全支持DOMSAXJAXP
Jsoup:jsoup 是一款Java 的HTML解析器 ,也可以解析XML PULL:Android内置的XML解析方式,类似SAX

dom4j:

介绍
    使用核心类SaxReader加载xml文档获得Document,通过Document 对象获得文档的根元素,然后就可以操作了
常用API:
    SaxReader对象
        read(...) 加载执行xml文档
    Document对象
        getRootElement() 获得根元素
    Element对象
        elements(...) 获得指定名称的所有子元素。可以不指定名称 
        element(...) 获得指定名称的第一个子元素。可以不指定名称 
        getName() 获得当前元素的元素名
        attributeValue(...) 获得指定属性名的属性值 
        elementText(...) 获得指定名称子元素的文本值
        getText() 获得当前元素的文本内容
xpath方式读取xml
介绍
    XPath 是一门在 XML 文档中查找信息的语言。 可以是使用xpath查找xml中的内容。
    由于DOM4J在解析XML时只能一层一层解析,所以当XML文件层数过多时使用会很不方便,结合 XPATH就可以直接获取到某个元素
XPath基本语法介绍
    使用dom4j支持xpath的操作的几种主要形式
        /AAA/DDD/BBB
        BBB[1] , BBB[last()]
        //BBB[@id='b1']
常用方法:
    selectSingleNode(query): 查找和 XPath 查询匹配的一个节点。参数是Xpath 查询串。
    selectNodes(query): 得到的是xml根节点下的所有满足 xpath 的节点;参数是Xpath 查询串。
    Node: 节点对象

JDBC自定义XML:

定义配置文件
    创建自定义xml 文件, 保存 数据库连接信息
使用:
    使用xpath 读取数据库信息
    SAXReader sr = new SAXReader();
    Document document = sr.read
    document.selectSingleNode

JVM介绍:

JVM组成:

组成:

类装载器子系统、运行时数据区、执行引擎、本地方法接口。
    其中执行引擎包含:解释器、及时编译器、垃圾回收器

架构图:

image-20211217173903222image-20211217173903222

生命周期:

虚拟机启动:

Java虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是由虚拟机的具体实现指定的。
jvm启动流程:
    1.JVM运行环境的设置和检查。
    2.通过CreateExecutionEnvironment函数查找JAVA_DLL动态库是否存在,能不能访问。并设置一些变量。
    3.加载动态库,将动态库中的一些函数链接至本地变量。
    4.解析[options][args]参数。
    5.新建线程初始化虚拟机。
    6.加载并执行主类。

虚拟机执行:

执行一个所谓的Java程序的时候,真真正正在执行的是一个叫做Java虚拟机的进程

虚拟机退出:

1.程序正常执行结束。
2.程序在执行过程中遇到了异常或错误而异常终止。
3.由于操作系统用现错误而导致Java虚拟机进程终止。
4.某线程调用Runtime类或system类的exit方法,或Runtime类的halt方法,并且java安全管理器也允许这次exit或halt操作。
5.除此之外,JNI(Java Native Interface)规范描述了用JNI Invocation API来加载或卸载 Java虚拟机时,Java虚拟机的退出情况。

代码执行过程:

Java源码  --编译->   字节码文件  --> 类加载器 -> 字节码校验器  --> 解析执行和编译执行

类加载:

类加载器:

类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。
ClassLoader类:
    概述:
        抽象类,bootstrap加载器外的加载器都继承于它 
    常用方法:
        getParent(String name)
        loadClass(String name)加载name的类,内部会调用findClass
        findClass(String name)查找指定的类。一般调用defindClass
        findLoadedClass(String name)查找已经加载过的类
        defindClass(String name, byte[] b,int off,int len)将字节数组转换为Java类
        resolveClass(Class<?> c)连接一个指定的类

第一个:启动类/引导类:Bootstrap ClassLoader
    使用C++语言实现,加载java的核心库,提供jvm自身需要的类。
    加载扩展类和应用程序类加载器,并指定为它们的父类加载器。
    出于安全考虑,只加载包名为java,javax,sum等开头的类
第二个:扩展类加载器:Extension ClassLoader
    java语言编写,内置类,继承自ClassLoader
    从java.ext.dirs系统属性所指定目录和从JDK的安装目录的jre/lib/ext子目录加载类库,用户创建的jar放在此目录下会被该类加载器加载。
第三个:应用程序类加载器:Application Classloader
    sun.misc.Launcher包下。
    加载环境变量classPath或者系统属性java.class.path指定的目录的类库。
    程序中默认的类加载器,我们Java程序中的类,都是由它加载完成的。
第四个:自定义加载器
    继承java.lang.ClassLoader类,重写findClass()方法,不建议覆盖loadClass方法(比如里面有双亲委派机制的逻辑,用于加载jdk内置类)。
    需求:
        隔离类加载
        修改类的加载方式
        扩展加载源,比如数据库
        防止源码泄漏,通过加载器加密解密类。
    findCLass实现细节:
        1.根据名字获取类的字节码(可以在这一步解密)
        2.调用protected方法defineClass(name,bytes,0,bytes.length)

类装载方式:

类的加载时机:
加载时机:new、对该类型静态变量的读写、对该类型静态方法的调用,还有反射
程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的。
隐式加载(非传入类全限定名的加载)、静态加载(编译时类不可以缺席的加载):
A b = new A();    如果程序运行到这段代码时还没有A类,那么JVM会请求装载当前类的类装器来装载类。没有用到的类不会加载(可以代码块测试)。(但import的类都会编译)
显式加载(传入类全限定名的加载)、动态加载(编译时类可以缺席的加载):
ClassLoader.loadClass(className) :只执行装载过程。
Class.forName(className):执行类的装载、链接、初始化过程。
使用装载当前类的装载器
    Class.forName("test.A");
使用某个类的装载器
    Class.forName("test.A",true,this.getClass().getClassLoader());
使用类路径类装载装载
    ClassLoader.getSystemClassLoader().loadClass("test.A");
使用当前进程上下文的使用的类装载器进行装载,这种装载类的方法常常被有着复杂类装载体系结构的系统所使用。
    Thread.currentThread().getContextClassLoader().loadClass("test.A")
使用自定义的类装载器装载类

过程:

加载:
1.根据类全限定名获取class字节码文件。
2.将字节流所代表的静态存储结构通过类加载器转化为元空间的运行时数据结构。
3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
字节码来源:
    一般的加载来源包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译
类加载器:
    一般包括启动类加载器,扩展类加载器,应用类加载器,以及用户的自定义类加载器。
链接
验证:
    主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。
    包括:
        对于文件格式的验证,比如常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息?
        对于元数据的验证,比如该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载?
        对于字节码的验证,保证程序语义的合理性,比如要保证类型转换的合理性。
        对于符号引用的验证,比如校验符号引用中通过全限定名是否能够找到对应的类?校验符号引用中的访问性(privatepublic等)是否可被当前类访问?
准备:
    主要是为类变量(注意,不是实例变量)分配元空间内存,并且赋予初值。
        特别需要注意,初值,不是代码中具体写的初始化的值,而是Java虚拟机根据不同变量类型的默认初始值。
    final修饰的static在编译阶段就分配了,在这个阶段显示初始化。
解析:
    将常量池内的符号引用替换为直接引用的过程。
初始化:
这个阶段主要是对类变量初始化,是执行类构造器cinit的过程。
换句话说,只对static修饰的变量或语句进行初始化。
如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
流程图:
image-20211217175622087image-20211217175622087
image-20211217175719395image-20211217175719395

双亲委托模型:

流程:
1.当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。
    每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。
2.当前classLoader的缓存中没有找到被加载的类的时候,
    委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader.
3.当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。
源码:
sun.misc.Launcher
流程图:
image-20211217175800501image-20211217175800501
原因:
JVM中要确定某一个类,需要类的 全限定名 + 加载此类的ClassLoader来共同确定。
也就是说即使两个类的全限定名是相同的,但是因为不同的ClassLoader加载了此类,那么在JVM中它是不同的类。
采用了委托模型以后加大了不同的ClassLoader的交互能力,无论你程序中有多少个类加载器,那么这些类其实都是可以共享的,这样就避免了不同的类加载器加载了同样名字的不同类以后造成混乱。
通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
优势:
1.避免类的重复加载
2.保护程序安全,防止核心API被随意篡改。
    同名的类会优先导入java本身的,同package的会报错。
    也被叫做沙箱安全机制。
类冲突:
现象:
多个版本的同一个类,默认情况下只会加载一次(因为缓存)。比如自定义String类,由于双亲委托,会返回jdk自带的。
类隔离:
前提:
JVM 在触发类加载时调用的是 ClassLoader.loadClass 方法。委托给父加载器查询,如果父加载器查询不到,就调用 findClass 方法进行加载。
类加载传导机制:
JVM 会选择当前类的类加载器来加载所有该类的引用的类。
概述:
利用类加载传导规则实现了不同模块的类隔离,这样不同模块之间的依赖就不会互相影响。
实现1:
自定义类加载器继承ClassLoader,通过自定义findClass方法来通过自定义path读取byte来加载。
适合场景:
    与内置类的全限定名不同的自定义类name,这样才会调用自定义的findClass方法。
    由于双亲委托,引用的类会被委托给根类加载器加载。所以像java.lang.String这些类都会最终加载到内置的String类。
实现2:
自定义类加载器,自定义loadclass方法,破坏双亲委派机制。
注意:
    由于类加载传导机制,所有类包括 java.lang 包里面的类都会通过该方法加载。
实现流程:
    1.先使用Thread.currentThread().getContextClassLoader().getParent()尝试loadClass,可以加载所有JDK自带的类。
        而自定义的类会报异常java.lang.ClassNotFoundExceptiontest.TestA
    2.然后通过path获取bytesdefineClass,加载自定义的类。
        注意:
            path必须是class文件的路径,不能是java文件

内存管理:

内存结构:

共享(需要GC):

堆(Heap):线程共享。所有的对象实例以及数组都要在堆上分配。回收器主要管理的对象。
方法区(Method Area):
    概述:
        线程共享。
        存储类型信息、域信息、方法信息、常量、静态变量(只是引用)、即时编译器编译后的代码。(JDK 1.8后hotspot称为元空间,存放在本地内存中)
    GC触发:
        到达初始元空间大小后,触发full gc,卸载没用的类,并重置高水位线。
    类型信息:
        类class、接口interface、枚举enum、注解annotation
        存储完整有效名称、父类的全名、修饰符、实现的接口的一个有序列表
    域信息:
        域名称、域类型、域修饰符(private这些)
    方法信息:
        方法名称、返回类型、参数数量和类型、修饰符、方法的(字节码、操作数栈和局部变量表的大小)、异常表(异常处理的开始位置、结束位置、代码的偏移地址、异常类的常量池索引)
    non-final类变量:
        静态变量,随着类的加载而加载。(initialization阶段显式声明,执行static代码块)
    static-final变量:
        在编译的时候就被分配了。class文件有值的声明。
    class文件常量池:
        反编译输出中的Constant pool。
        Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用保存在该常量池。
        描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的。
        动态链接可以将这些符号引用转换为调用方法的直接引用。
        包含了各自字面量和对类型、域和方法的符号引用。通过这个引用找到对应的内容。
        组成:
            字符串常量
            数字常量
            类引用
            方法引用
            字段引用
    字符串常量池:
        详见字符串常量池。准备阶段生成。
    运行时常量池:
        概述: 
            jvm在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。
            而当类加载到内存中后,jvm就会将 class常量池 中的内容存放到运行时常量池中,由此可知,运行时常量池 也是每个类都有一个。
            包含多种不同的常量字面量,比如编译器就已经明确的数值字面量,以及到运行期解析后把符号引用替换为直接引用。
            解析的过程会去查询字符串常量池,也就是StringTable,以保证 运行时常量池所 引用的字符串与 字符串常量池 中所引用的是一致的。

线程私有:

虚拟机栈(JVM Stack):
概述:
线程私有。内部保存着一个个的栈帧stack frame,对应一次次的Java方法调用。每个栈帧存储局部变量表、操作栈、动态链接、方法返回地址,对象指针。
大小可以是动态(OutOfMemoryError)或者固定的(在线程创建的时候独立设置,超出会StackoverflowError)。
动态链接:
    指向运行时常量池的方法引用
操作栈:
    表达式栈。某些指令将指压入操作数栈,其余指令将操作数取出栈,使用它们后再把结果压入栈。
    保存计算过程的中间结果,同时存储临时变量。
对比CPU寄存器实现的优点:
跨平台,指令集小,编译器容易实现。
本地方法栈(Native Method Stack):
线程私有。为虚拟机使用到的Native方法服务。如Java使用c或者c++编写的接口服务时,代码在此区运行。
程序计数器(Program Counter Register):
概述:
线程私有。有些文章也翻译成PC寄存器(PC Register),同一个东西。
作用:
存储当前线程所执行的java方法字节码的行号,如果是native方法则是undefined
它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

结构图:

image-20220213191140338image-20220213191140338

内存分配位置:

局部变量:

基本数据类型:
变量名和值都在方法栈
引用数据类型:
变量在虚拟机栈上。内存逃逸的情况下对象分配到堆上,否则放到栈上(标量替换)。

成员变量:

基本数据类型:
变量名和值都在堆上
引用数据类型:
变量名和变量指向的对象都在堆上

JMM内存模型:

概述:

Java虚拟机规范中定义了Java内存模型(Java Memory Model,JMM),用于屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果。
JMM规范了Java虚拟机与计算机内存是如何协同工作的:
    规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。
    围绕特征:原子性,可见性,有序性。
    遵守的两个规则:
        as-if-serial规则保证了不管如何重排执行结果不能被改变
        happens-before规则向程序员保证了代码的执行顺序,但实际具体执行顺序由JMM处理。

内存划分:

JVM将内存组织为主内存和工作内存两个部分。
主内存主要包括本地方法区和堆。
每个线程都有一个工作内存,工作内存中主要包括两个部分,一个是属于该线程私有的栈和对主存部分变量拷贝的寄存器(包括程序计数器PC和cup工作的高速缓存区)。
硬盘 -> 内存 -> CPU三级缓存 -> CPU

具体规范:

1.所有的变量都存储在主内存中(虚拟机内存的一部分),对于所有线程都是共享的。
2.每条线程都有自己的工作内存,工作内存中保存的是主存中某些变量的拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。
3.线程之间无法直接访问对方的工作内存中的变量,线程间变量的传递均需要通过主内存来完成。

8种内存交互操作:

image-20220110172420700image-20220110172420700
lock(锁定),作用于主内存中的变量,把变量标识为线程独占的状态。
read(读取),作用于主内存的变量,把变量的值从主内存传输到线程的工作内存中,以便下一步的load操作使用。
load(加载),作用于工作内存的变量,把read操作主存的变量放入到工作内存的变量副本中。
use(使用),作用于工作内存的变量,把工作内存中的变量传输到执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
assign(赋值),作用于工作内存的变量,它把一个从执行引擎中接受到的值赋值给工作内存的变量副本中,每当虚拟机遇到一个给变量赋值的字节码指令时将会执行这个操作。
store(存储),作用于工作内存的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用。
write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

as-if-serial规则:

概述:
不管怎么重排序,单线程下的执行结果不能被改变。
编译器、runtime和处理器都必须遵守as-if-serial语义。
MESI协议:
在早期的CPU中,是通过在总线加LOCK#锁的方式实现的,但是这种方式开销太大,所以Intel开发了缓存一致性协议,也就是MESI协议。
该缓存一致性思路:当CPU写数据时,如果发现操作的变量时共享变量,即其他线程的工作内存也存在该变量,于是会发信号通知其他CPU该变量的内存地址无效。
当其他线程需要使用这个变量时,如内存地址失效,那么它们会在主存中重新读取该值。
内存屏障:
背景:
存储缓存和失效队列的引入在提升MESI协议实现的性能同时,也带来了一些问题。
由于MESI的高速缓存一致性是建立在强一致性的总线串行事务上的,而存储缓存和失效队列将事务的强一致性弱化为了最终一致性,
使得在一些临界点上全局的高速缓存中的数据并不是完全一致的。
                  对于一般的缓存数据,基于异步最终一致的缓存间数据同步不是大问题。
但对于并发程序,多核高速缓存间短暂的不一致将会影响共享数据的可见性,使得并发程序的正确性无法得到可靠保证,这是十分致命的。
但CPU在执行指令时,缺失了太多的上下文信息,无法识别出缓存中的内存数据是否是并发程序的共享变量,是否需要舍弃性能进行强一致性的同步。
所以,CPU的设计者提供了内存屏障机制将对共享变量读写的高速缓存的强一致性控制权交给了程序的编写者或者编译器。
概述:

又称内存栅栏,是一个CPU指令,基本上它是一条这样的指令:

保证特定操作的执行顺序。
影响某些数据(或则是某条指令的执行结果)的内存可见性。
分类:
LoadLoad屏障:对于这样的语句Load1,LoadLoad,Load2。在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:对于这样的语句Store1,StoreStore,Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
LoadStore屏障:对于这样的语句Load1,LoadStore,Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
StoreLoad屏障:对于这样的语句Store1,StoreLoad,Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。
原理:
在每个volatile读操作后插入LoadLoad屏障和LoadStore屏障。
在每个volatile写操作的前面插入一个StoreStore屏障,后面插入一个SotreLoad屏障。
作用:
编译器和CPU能够重排序指令,保证最终相同的结果,尝试优化性能。插入一条Memory Barrier会告诉编译器和CPU:不管什么指令都不能和这条Memory Barrier指令重排序。
Memory Barrier所做的另外一件事是强制刷出各种CPU cache,如一个Write-Barrier(写入屏障)将刷出所有在Barrier之前写入 cache 的数据,
因此,任何CPU上的线程都能读取到这些数据的最新版本。

happens-before原则:

概述:
JVM底层的内存操作原则
可以保证正确同步的多线程程序的执行结果不被改变。
分类:
单线程happen-before原则:在同一个线程中,书写在前面的操作happen-before后面的操作。
锁的happen-before原则:同一个锁的unlock操作happen-before此锁的lock操作。
volatile的happen-before原则:对一个volatile变量的写操作happen-before对此变量的任意操作(当然也包括写操作了)。
happen-before的传递性原则:如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。
线程启动的happen-before原则:同一个线程的start方法happen-before此线程的其它方法。
线程中断的happen-before原则:对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。
线程终结的happen-before原则:线程中的所有操作都happen-before线程的终止检测。
对象创建的happen-before原则:一个对象的初始化完成先于他的finalize方法调用。

volatile关键字:

背景:
在并发编程中,我们通常会遇到以下三个问题:原子性问题,可见性问题,有序性问题。
原子性:
    描述: 
        并发读取再修改,不是原子性的(最终一致性。)。
    原因: 
        读取的时候拷贝到工作内存上。
    解决: 
        Atomic包,原理是CAS+version,比如可以借助硬件的多核Cache一致性协议MESI
        synchronized和Lock
可见性:
    背景: 
        线程1对变量i修改了之后(仍处于工作内存如CPU高速缓存中),线程2没有立即看到线程1修改的值。
        示例: 
            //线程1修改变量的值
            //线程2 
            while (a == 初始值) {}  //可以看到线程2一直等待
    解决:
        volatilesynchronized和Lock(释放锁之前会将对变量的修改刷新到主存当中)。
有序性:
    背景:
        CPU会对指令重排序,确保单个线程的重排序不会影响执行的结果。但多线程仍然可能会影响。
        示例: 
            //线程1:
            context = loadContext();   //语句1
            inited = true;             //语句2,如果使用volatile修饰,那么不会线程1不会将语句1,2重排。
            //线程2:
            while(!inited ){
              sleep()
            }
            doSomethingwithconfig(context);
        示例2
            单例模式DCL中,读取的instance不为null,但重排后有可能引用指向的对象还没有完成初始化。
            instance = new SingletonDemo()可以分为 
                memory = allocate() 分配对象的内存空间
                instance(memory);   初始化对象
                instance = memory;  设置instance指向刚分配的内存地址。 
            默认情况下语句2和语句3不存在依赖关系,有可能经过重排,导致其他线程拿到的instance还没初始化完成。

    解决:
        volatile可以保证一定的有序性。在进行指令优化时,不能将在对volatile变量访问(赋值)前面的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
            系统将不允许对 写入一个volatile变量的操作与其之前的任何读写操作 重新排序,也不允许将 读取一个volatile变量的操作与其之后的任何读写操作 重新排序。
        synchronized和Lock。
概述:
Java虚拟机提供的轻量级的同步机制。
保证可见性:
    修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。
    这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。(可见性)
禁止指令重排,有序性
不保证原子性
实现原理:
对于volatile字段,Java内存模型将在写操作后插入一个写屏障指令,在读操作前插入一个读屏障指令。(通过写屏障保证可见性)
屏障两边的指令禁止重排。(有序性)
作用:
保证可见性、有序性
和 CAS 结合,保证了复合操作的原子性
修饰longdouble可以保证其读写单个操作原子性。(否则,在操作的时候,可以分成两步,每次对32位操作。)
限制:
volatile只能针对基础类型,如果是引用类型,不过只是一个指向数组的引用,而不是整个数组。
意思是,如果改变引用指向的数组,将会受到 volatile 的保护,但是如果多个线程同时改变数组的元素,volatile 标示符就不能起到之前的保护作用了。
实践:
1.单例模式Double-Check的解决
volatile保证可见性的流程:
image-20220110172705353image-20220110172705353

JVM堆内存:

结构:

JVM内存划分为堆内存和非堆内存,堆内存分为年轻代(Young Generation)、老年代(Old Generation),非堆内存就一个MetaSpacejava8之前为永久代)。    
年轻代又分为Eden生成区和Survivor区。Survivor区由FromSpaces0)和ToSpaces1)组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1
堆内存用途:
    存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收。
非堆内存用途:
    永久代,也称为方法区,存储程序运行时长期存活的对象,比如类的元数据、方法、运行时常量池、静态变量等。
    几乎不进行垃圾回收。
    JDK1.8版本废弃了永久代,替代的是元空间(MetaSpace),元空间与永久代上类似,都是方法区的实现,他们最大区别是:元空间并不在JVM中,而是使用本地内存(所以-XX指定的内存不包含)。
    配置:
        -XX:MetaspaceSize, 默认20m,当触及后会Full GC,然后调整size直到MaxMetaspaceSize
        -XX:MaxMetaspaceSize,默认无限制
    k8s内存分配:
        考虑-Xmxmetaspace,程序本身运行消耗。

结构图:

image-20211217180223342image-20211217180223342

对象分代:

1.新生成的对象首先放到年轻代Eden区。(如果放不下,YGC后仍然放不下就直接放入老年区)
2.当Eden空间满了,触发Minor GC(清理年轻代),存活下来的对象移动到Survivor0区。
3.Survivor0区满后触发执行Minor GCSurvivor0区存活对象移动到Suvivor1区,这样保证了一段时间内总有一个survivor区为空。
    经过多次Minor GC(默认15次)仍然存活的对象移动到老年代。
4.老年代存储长期存活的对象,占满时会触发Major GCFull GC),GC期间会停止所有线程等待GC完成,所以对响应要求高的应用尽量减少发生Major GC,避免响应超时。

JVM参数:

标配参数:
-version
-help
-showversion
X参数:
-Xint 解释执行
-Xcomp 先编译后执行
-Xmixed 混合模式,默认
XX参数:
查看配置:
方式一:jps + jinfo -flag 属性名/-flags 进程号
方式二:
    java -XX:+PrintFlagsInitial 查看初始化参数,
    -XX:+PrintFlagsFinal 查看修改后参数。:=标记为修改过的参数
    -XX:+PrintCommandLineFlags 打印传递给虚拟机的显式和隐式参数
布尔类型:
-XX:+(开启)或-(关闭)某个属性值
值类型:
-XX:属性值key=属性值value
常用参数:
-Xms<size>           -XX:InitialHeapSize,Heap初始值,等价于Runtime.getRuntime().totalMemory()
-Xmx<size>           -XX:MaxHeapSizeJava,Java Heap最大值。Runtime.getRuntime().maxMemory(),bytes单位
-Xss<size>            -XX:ThreadStackSize,设置每个线程可使用的初始内存大小,即栈的大小。
-Xmn<size>          年轻代大小
-XX:NewRatio        设置Yong 和 Old的比例
-XX:Surviorratio      设置Eden和一个Suivior的比例
-XX:MetaspaceSize 
-XX:MaxMetaspaceSize 
-XX:MaxTenuringThreshold  转为老年代的回收次数,0-15范围。越大可以增加对象被回收的概率,减轻full gc
-XX:+PrintGCDetails  
    [GC (Allocation Failure) [PSYoungGen: 53248K->2176K(59392K)] 58161K->7161K(256000K), 0.0039189 secs] [Times: user=0.02 sys=0.01, real=0.00 secs]
    GC类型  失败类型          年轻代    gc前大小 gc后大小 总大小  gc前堆大小  gc后 总大小  gc用时            用户耗时 系统耗时  实际耗时
    如果是full gc,将会打印年轻代、老年代、元空间的大小。
-XX:MaxDirectMemorySize=5m   默认机器内存的1/4
-XX:+PrintGCTimeStamps
-XX:+HeapDumpOnOutOfMemoryError  默认导出到当前路径
-XX:HeapDumpPath

JVM自带分析工具:

jps(虚拟机进程状态工具)
jinfo(实时查看和修改java配置信息工具)
jmap(内存映像工具):
    生成堆的快照和对象统计信息,生成hprof文件后用mat分析工具。
jstat(统计信息监控工具):
    类加载统计、编译统计、垃圾回收统计
    jstat -gc -t 58563 #显示pid是58563的垃圾回收堆的行为统计
    线上问题排查:
        jstat实时查看分析
        堆溢出时自动堆转存-XX:+HeapDumpOnOutOfMemoryError,通过工具jhat分析。
            -XX:HeapDumpPath=./java_pid<pid>.hprof
jstack(堆栈异常跟踪工具)
jvisualvm 
jconsole

常见内存错误:

1.java.lang.StackOverflowError
栈溢出Error,属于错误,常见递归。
2.java.lang.OutOfMemoryError:Java heap space
对象分配过程中,堆内存不足。
3.java.lang.OutOfMemoryError:GC overhead limit exceeded
gc回收时间过长抛出,定义是超过98%的时间用来gc且回收不到2%的堆内存。
连续多次GC都只回收不到2%的情况下才会抛出,如果不抛出这个错误,GC清理的内存很快被填满,导致gc再次执行,CPU使用率一直100%。
overhead内存:
    主要指创建对象本身需要的内存,不包含对象数据。比如arrayList的总内存要大于列表元素的总和。
4.java.lang.OutOfMemoryError:Direct buffer memory
nio程序经常使用byteBuffer来读取或者写入数据,这是一种基于通道channel与缓冲区bufferio方式。
它可以使用native函数库直接分配堆外内存,然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。
可以在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。
    ByteBuffer.allocate(capability)第一种方式是分配JVM堆内存,属于GC管辖范围,由于需要拷贝相对较慢
    ByteBuffer.allocateDirect(capability)分配os本地内存,不属于GC管辖范围,由于不需要拷贝速度快。
但如果不但分配本地内存,堆内存很少使用,JVM就不需要执行GCDirectBuffer对象不会进行回收,这时候堆内存充足,但本地内存可能已经使用光了,
再次尝试分配本地内存就会出现OutOfMemoryError,程序崩溃。
5.java.lang.OutOfMemoryError:unable to create new native thread
linux默认创建的线程数量是1024个
ulimit -u 
6.java.lang.OutOfMemoryError:metaspace

如何判断对象是否可回收?

1.引用计数器算法:

每当对象被引用一次计数器加 1,对象失去引用计数器减 1,计数器为 0 是就可以判断对象死亡了。
这种算法简单高效,但是对于循环引用或其他复杂情况,需要更多额外的开销,因此 Java 几乎不使用该算法。

2.根搜索算法-可达性分析算法:

所谓可达性分析是指,顺着 GCRoots 根一直向下搜索,整个搜索的过程就构成了一条“引用链”,只要在引用链上的对象叫做可达,
在引用链之外的(说明跟 GCRoots 没有任何关系)叫不可达,不可达的对象就可以判断为可回收的对象。
哪些对象可作为 GCRoots 对象呢? 
    虚拟机栈帧上局部变量表中直接or间接引用的对象(方法参数、局部变量、临时变量)
    方法区中的静态属性引用类型对象、常量引用对象
    本地方法栈中的引用对象(Native 方法的引用对象)
    Java 虚拟机内部的引用对象,如异常对象、系统类加载器等
    所有被同步锁(synchronize)持有的对象
    Java 虚拟机内部情况的注册回调、本地缓存等
利用MAT插件分析GC Roots:
利用Jprofile实现GC Roots溯源:
system.gc()理解不可达对象的回收行为:
    垃圾回收时obj=null
    方法执行完返回后,局部变量会被回收
    代码块内的局域变量,代码块执行完后,gc前有其他变量声明,那么代码块的局域变量会被回收
内存泄露:
    概述: 
        对象不会再被程序使用,但GC又不能回收的情况。
        更宽泛意义上,由于不严谨导致对象的生命周期比预期长甚至导致OOM,也成为宽泛意义上的内存泄露。
    出现的情况:
        静态集合类:
            因为静态,生命周期和JVM一致,里面保存的对象在程序结束前不能释放。
        单例模式:
            单例模式本身静态,生命周期长。如果引用了外部对象也不能释放。
        内部类持有外部类:
            一个外部类的实例对象的方法返回了内部类的实例对象,如果内部类对象长期存在,那么外部类就算不使用也不能回收。
        提供close方法的资源未关闭:
            提供close方法的资源未关闭导致内存泄露,比如数据库连接、socket
        变量不合理的作用域:
            一个变量的定义的作用范围大于其使用范围的时候
        改变哈希值:
            当一个对象存储到HashSet中后,再修改参与计算哈希值的字段,导致无法删除。这也是string为什么不可变的原因,可以放心使用string。
        缓存泄露:
            将对象放入缓存后,忘记清理。推荐用weakHashMap
        监听器和回调:
            注册回调没用取消,就会积聚。推荐保存弱引用。
stop-the-world:
    概述: 
        可达性分析算法中枚举根节点(GC Roots)会导致所有Java执行线程停顿。
        所有的GC回收器都有stw。
    安全点与安全区域:
        安全点:
            只有特定的位置才能停顿下来开始GC,这些位置称为安全点。
            safe point的选择很重要,如果太少会导致GC等待的时间太长。如果太频繁可能会导致运行时的性能问题。
            大部分指令的执行时间非常短暂,通常会根据是否具有让程序长时间执行的特征为标准,比如选择一些执行时间较长的指令作为safe point,如方法调用、循环跳转、异常跳转等。
        如何检查所有线程都跑到最近的安全点停顿了?
            抢先式中断:
                中断所有线程,如果还有线程不在安全点,就恢复线程让其跑到安全点
            主动式中断:
                设置中断标志,各个线程运行到SafePoint的时候主动轮询这个标志,如果为真则自己中断挂起。
        安全区域:
            safepoint需要程序执行,遇到sleep或者blocked的状态,无法响应JVM的中断请求,走到safepoint,而jvm也不能等待线程被唤醒。
            对于这种情况,需要安全区域来解决。
            安全区域指一段代码片段中,对象的引用关系不会发生变化,在这个区域的任何位置开始GC都是安全的。
            可以理解为扩展的safepoint。
            进入safeRegion的线程会标识自己,GC的时候jvm会忽略这些线程。
            当线程即将离开safe region的时候,会检查JVM是否已经完成GC,如果还没完成,会等待信号。

垃圾回收算法:

1.标记-清除算法(老年代)

概述:
根据名称就可以理解改算法分为两个阶段:首先标记出所有需要被回收的对象,然后对标记的对象进行统一清除,清空对象所占用的内存区域。
速度中等。
缺点:
第一个:是执行效率不可控,试想一下如果堆中大部分的对象都可回收的,收集器要执行大量的标记、收集操作。
第二个:产生了许多内存碎片,通过回收后的内存状态图可以知道,被回收后的区域内存并不是连续的,当有大对象要分配而找不到满足大小的空间时,要触发下一次垃圾收集。

2.标记-复制算法(常用于年轻代)

概述:
标记-复制算法将内存分为大小相同的两个区域,运行区域,预留区域,所有创建的新对象都分配到运行区域,当运行区域内存不够时,将运作区域中存活对象全部复制到预留区域,
然后再清空整个运行区域内存,这时两块区域的角色也发生了变化,每次存活的对象就像皮球一下在运行区域与预留区域踢来踢出,而垃圾对象会随着整个区域内存的清空而释放掉
eden区、幸存区S0S1Eden:From:To区域的比例是8:1:1,始终有90%的空间是可以用来创建对象的,而剩下的10%用来存放回收后存活的对象。
万一to区的内存不足,会借助老年代的空间。
速度最快。
流程:
1、当Eden区满的时候,会触发第一次young gc,把还活着的对象拷贝到Survivor From区;当Eden区再次触发young gc的时候,会扫描Eden区和From区域,对两个区域进行垃圾回收,
经过这次回收后还存活的对象,则直接复制到To区域,年龄+1,并将Eden和From区域清空。交换s0和s1。
2、当后续Eden又发生young gc的时候,会对Eden和To区域进行垃圾回收,存活的对象复制到From区域,并将Eden和To区域清空。
3、可见部分对象会在FromTo区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代。
优点:
标记-复制算法在大量垃圾对象的情况下,只需复制少量的存活对象,并且不会产生内存碎片问题,新内存的分配只需要移动堆顶指针顺序分配即可,很好的兼顾了效率与内存碎片的问题。
缺点:
预留一半的内存区域未免有些浪费了
如果内存中大量的是存活状态,只有少量的垃圾对象,收集器要执行更多次的复制操作才能释放少量的内存空间,得不偿失。
如果存活对象的数量比较大,coping的性能会变得很差。

3.标记-整理算法(老年代)

概述:
标记-复制算法要浪费一半内存空间,且在大多数状态为存活状态时使用效率会很低,针对这一情况计算机科学家又提出了一种新的算法“标记-整理算法”,
标记整理算法的标记阶段与其他算法一样,但是在整理阶段,算法将存活的对象向内存空间的一端移动,然后将存活对象边界以外的空间全部清空。
速度最慢。比复制算法多了标记的阶段,比标记清除多了整理内存的阶段。
优点:
不仅可以弥补标记/清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价
缺点:
标记整理算法解决了内存碎片问题,也不存在空间的浪费问题,看上去挺美好的。
但是,当内存中存活对象多,并且都是一些微小对象,而垃圾对象少时,要移动大量的存活对象才能换取少量的内存空间。效率不高。(耗时)

4.分代收集算法

目前几乎所有的垃圾回收器都是基于分代算法。
新生代:复制算法(CG后只有少量的对象存活)
老年代:标记-整理算法 或者 标记-清理算法(GC后对象存活率高)

5.增量收集算法:

概述:
上面三种基本算法,都需要stop the world。
基本思想是让垃圾回收线程和用户线程交替执行,垃圾回收线程每次只回收一小片区域的内存空间,再切换用户线程,直到回收完成。
以三种传统算法为基础,妥善处理线程间冲突,允许线程以分阶段的方式完成标记、清理或复制工作。
缺点:
吞吐量下降

6.分区算法:

概述:
一般来说,堆空间越大,GC需要的时间越长,停顿也越久。为了更好的控制停顿时间,将一块内存区域分为多个小块。根据目标停顿时间,每次合理回收若干个小区域。

垃圾收集器:

Serial收集器(复制算法):

新生代单线程收集器,会暂停所有的用户线程。
配置: 
    -XX:+UseSerialGC  

Serial Old收集器 (标记-整理算法):

老年代单线程收集器,Serial收集器的老年代版本;标记和清理都是单线程,优点是简单高效;
配置: 
    -XX:+UseSerialOldGC  java8已经废弃 

ParNew收集器 (复制算法):

新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;垃圾回收的过程用户线程会暂停。
除了Serial收集器之外,只有它能与CMS收集器配合工作。
配置: 
    -XX:+UseParNewGC     
    -XX:ParallelGCThreads

Parallel Scavenge收集器 (复制算法):

概述:
新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间)
适合场景:
高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
吞吐量 = 运行用户代码时间/运行用户代码时间 + 垃圾回收时间
与ParNew区别:
GC框架框架不同,所以不能与CMS搭配。
自适应调节策略:
    根据当前的运行情况收集性能监控信息,动态调整参数以满足MaxGCPauseMillisGCTimeRatio
    Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。
配置:
-XX:+UseParallelGC
-XX:ParallelGCThreads

Parallel Old收集器 (标记-整理算法):

概述:
老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
流程:
Mark阶段:串行标记所有从GC Roots可直达的对象,然后并行标记所有存活的对象。
Summary:串行执行,从左至右计算各个Region的密度,直到找到一个point,这个point左侧的Region都不值得整理,右侧的Region需要整理。
Compaction:利用Summary阶段的统计数据,针对需要整理的部分,采用“整理”算法进行并行操作。
配置:
-XX:+UseParallelOldGC  可互相激活

CMS(Concurrent Mark Sweep)收集器(标记-清除算法):

概述:
老年代并发concurrent收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
用户线程与gc线程同时执行,停顿较少。
流程:
初始标记(CMS initial mark) -stop the world
并发标记(CMS concurrent mark) (用户线程和gc线程并行执行)
重新标记(CMS remark) -stop the world,修正用户线程运行导致标记变动的对象的标记
并发清除(CMS concurrent sweep)(用户线程和gc线程并行执行)
优点:
并发收集、低停顿。
适合重视服务的响应速度,希望系统停顿时间最短的互联网站。
缺点:
CMS收集器对CPU资源非常敏感。(占用多)
CMS收集器无法处理浮动垃圾(Floating Garbage)。由于CMS并发清理时,用户线程还在运行,伴随产生新垃圾,而这一部分出现在标记之后,只能下次GC时再清理。这一部分垃圾就称为”浮动垃圾“。
    需要预留空间给并发收集时用户程序产生的新对象。
CMS收集器是基于标记-清除算法,该算法的缺点都有。
    比如内存碎片,当大对象没有空间时,需要FullGC时开启内存碎片合并整理过程,这个过程无法并发,需要停顿。
CMS会占用堆内存,当发生promotion failed和concurrent mode failure时,会触发Full GC,切换到Serial Old(从未移植parallel old,而g1在java10中已经实现)
    promotionfailed是在进行Minor GC时,survivor space放不下、对象只能放入旧生代,而此时旧生代也放不下造成的;
    concurrent mode failure是在执行CMS GC的过程中同时有对象要放入旧生代,而此时旧生代空间不足造成的。
配置:
-XX:+UseConcMarkSweepGC : 手动指定老年代使用CMS收集器
-XX:+UseCMSCompactAtFullCollection : 在进行Full GC时对内存进行压缩,JDK1.6以前不需要配置,默认开启
-XX:+UseParNewGC
-XX:CMSFullGCsBeForeCompaction= 指定多少次cms收集之后,进行一次压缩的full gc。默认0,每次都整理。

G1(Garbage First)收集器 (新生代并行复制 + 老年代并发标记、增量整理算法):

以前收集器特点:
年轻代和老年代是各自独立且连续的内存块。
年轻代收集使用单eden+so+s1的复制算法
老年代收集必须扫描整个老年代区域
都是以尽可能少而快速执行gc为设计原则。
概述:
G1收集器是JDK1.7提供的一个新收集器,面向服务端应用,实现高吞吐量的同时,尽可能减少停顿时间。
特点:
CMS收集器一样,能与应用程序线程并发执行。
整理空闲空间更快。
需要更多的时间来预测GC停顿时间。
不希望牺牲大量的吞吐性能。
不需要更大的Java Heap。
G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。
如何优于CMS:
G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。
STW更可控(region更小),在停顿时间上添加了预测机制,用户可以指定期望停顿时间。
实现原理:
G1将堆分成许多相同大小的区域单元,每个单元称为Region。避免了全区域的GC操作。
虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。
每个分区可以在老年代和年轻代之间切换,大小可以通过参数指定,范围1-32MB,2的幂,默认将堆划分为2048个分区,最多2048个分区,最大内存支持64G。
区域region划分:
Eden regions(年轻代-Eden区)
Survivor regions(年轻代-Survivor区)
Old regions(老年代)
Humongous regions(巨型对象区域)- 存放超过region一半大小的对象,会寻找连续的H区,不够的情况下会启动full gc。
Free resgions(未分配区域,也会叫做可用分区)-上图中空白的区域
回收流程:
年轻代GC(Young GC)
    eden区耗尽后触发,主要是小区域收集+空出来连续的内存块,避免内存碎片。并行执行,stop the world:
        eden区数据移动到Survivor区,假如Survivor区空间不够,eden区数据会晋升到Old区。
        Survivor区移动到另一部分的Survivor区,部分晋升到Old区。
        Eden区清理完成后,GC结束,用户的应用程序继续执行。
全局并发标记过程(global Concurrent Marking,伴随多次young GC)
    触发条件:
        堆内存使用到达阈值(InitiatingHeapOccupancyPerent默认45%)
    流程:
        初始标记,标记GC Roots直接关联到的对象,stw
        并发标记
        最终标记stw
        并发清理,识别并清理完全空闲的区域。
混合回收(Mixed GC)
    老年代空间越来越少时触发。
    选取所有的 Young region + 收益高的若干个 Old region。
记忆集和写屏障:
无论是G1还是其他分代收集器,JVM都是使用Remembered Set来避免全局扫描。
每个Region都有一个对应的Remembered Set,每次Reference类型数据写操作时,都是产生一个Writebarrier屏障暂时中断操作。
然后检查将要写入的引用指向的对象是否和该Reference类型数据在不同的region(其他收集器的做法是检查老年代是否引用了年轻代对象)
如果不同,通过CardTable把相关引用信息记录到引用指向对象的所有region的remembered set中。
当进行GC的时候,只需要扫描GC Roots的region + remembered set的region即可。
优点:
G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。分区收集。
避免cms内存碎片问题的存在。
G1 Full gc在java10后使用了Parallel技术
缺点:
相当于CMS,G1还不具备压倒性的优势。
G1为了垃圾收集产生的内存占用,还是执行负载都比CMS高。
小内存应用上CMS的表现大概率会优于G1(执行负载和内存占用高),而G1在大内存应用上则发挥其优势。平衡点在6-8GB之间。
适合场景:
对 GC 的停顿时间很敏感,并具有大堆的应用,比如 WEB 服务器(替代CMS
超过50%的Java堆被活动数据占用,标记-清除算法会导致内存碎片化严重,所以标记-整理算法比较适合活动对象比较多的情况。
对象分配频率或年代提升频率变化很大
对GC停顿时间要求更加可控。
优化建议:
避免使用-Xmn或者-XX:NewRatio等相关选项显式设置年轻代大小。否则会覆盖目标暂停时间。
目标暂停时间谨慎选择,避免吞吐量下降。
配置:
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200        如果MaxGCPauseMillis设置的过小,那么GC就会频繁,吞吐量就会下降。如果MaxGCPauseMillis设置的过大,应用程序暂停时间就会变长。
-XX:G1HeapRegionSize=n           区域大小
-XX:InitiatingHeapOccupancyPerent=n  堆占用达到比例后触发GC,默认45 
-XX:ConcGCThreads=n    并发线程数量
-XX:G1ReversePercent=n 空闲空间的预留内存百分比。防止溢出。默认10%。

搭配关系:

image-20211220120428420image-20211220120428420
虚线为即将废弃的搭配。

查看默认垃圾回收器命令:

java -XX:+PrintCommandLineFlags -version
    java8使用的是UseParallelGC 即 Parallel Scavenge + Parallel Old
    java11使用的是UseG1GC
    Jvm client模式下新生代默认使用UseSerialGC,开启后使用Serialyoung区)+Serial Old收集器
    Jvm server模式下新生代默认使用UseParNewGC,开启后使用ParNewyoung区)+Serial Old收集器(已过时)
jmap -heap pid
jinfo -flag UseParallelGC 进程号
    查看线上服务

名词解释:

DefNew:Default new generation
Tenured: old 
ParNew: Parallel New Generation
PSYoungGen: Parallel Scavenge 
ParOldGen: Parallel Old Generation 
Server/Client模式:64位都是Server模式,32位资源够的情况下非window才是server模式

GC:

分类:

部分收集(Partial GC)。不是完整收集整个Java堆的垃圾收集
    新生代收集(Minor GC/Young GC):只是新生代的垃圾收集
    老年代收集(Major GC/Old GC):只是老年代的圾收集
        目前,只有CMS GC会有单独收集老年代的行为
        备注:很多时候 Major GC 会与Full GC 混用,需要具体分辨是老年代回收还是整堆回收
    混合收集(Mixed GC):收集整个新生代以及部分老年代的垃圾收集。目前,只有 G1 GC 会有这种行为
整堆收集(Full GC)。收集整个 java 堆和方法区的垃圾收集

触发机制和特点:

Minor GC触发条件和特点:
当Eden区满时,触发Minor GC。
Java对象大多都具备 朝生夕灭 的特性,所以Minor GC非常频繁,一般回收速度也比较快
Minor GC会引发STW,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行
老年代GC(Major GC)触发机制及特点:
指发生在老年代的GC
出现Major GC,经常会伴随至少一次的Minor GC
Major GC的速度一般会比Minor GC慢10倍以上,STW的时间更长
如果Major GC后,内存还不足,就报OOM
Full GC触发条件:
1)调用System.gc时,系统建议执行Full GC,但是JVM不必然执行
2)老年代空间不足
3)方法区空间不足
4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

查看gc情况:

jstat -gc 12538 1s

逃逸分析:

逃逸现象:

当变量(或者对象)在方法中分配后,其指针有可能被返回或者被全局引用,这样就会被其他方法或者线程所引用,这种现象称作指针(或者引用)的逃逸。
通俗的说,如果一个对象的指针被多个方法或者线程引用时,那么就称这个对象的指针(或对象)的逃逸(Escape)。

概述:

一种可以有效减少Java程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。
一种动态确定指针动态范围的静态分析,它可以分析在程序的哪些地方可以访问到指针,判断对象的作用域是否有可能逃出方法体。
通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围,从而决定是否要将这个对象分配到堆上。

逃逸的情况:

1.对象是否被存入堆中(静态字段或堆中对象的实例字段)
堆内存是线程共享的,一旦对象被分配在堆中,那所有线程都可以访问到该对象,这样即时编译器就追踪不到所有使用到该对象的地方了,这样的对象就属于逃逸对象
2.对象是否被传入未知代码中(方法的调用者和参数)
Java 的即时编译器是以方法为单位进行编译,即时编译器会把方法中未被内联的方法当成未知代码,所以无法判断这个未知方法的方法调用会不会将调用者或参数放到堆中,所以认为方法的调用者和参数是逃逸的

逃逸分类:

方法返回值:
方法返回值,可能在方法外被调用。
给成员变量赋值:
给成员变量赋值。可能在其他线程中访问的实例变量

编译器分析后的常见优化:

栈上分配:
将堆分配转化为栈分配,栈中的变量会在方法调用结束后自动销毁,无需垃圾回收。提高系统的性能。
标量替换:
标量(Scalar)是指一个无法再分解成更小的数据的数据。Java中的原始数据类型就是标量。
相对的,那些还可以分解的数据叫做聚合量(Aggregate),Java中的对象就是聚合量,因为他可以分解成其他聚合量和标量。
在JIT阶段,如果经过逃逸分析,发现一个对象不会被外界访问的话,那么经过JIT优化,就会把这个对象拆解成若干个其中包含的若干个成员变量来代替。这个过程就是标量替换。
不再需要分配堆内存。
为栈上分配提高了很好的基础。
同步消除:
线程同步是个相对耗时的过程,若逃逸分析能确定一个变量不会逃逸出线程,即不会被其他线程访问,则该变量的读写肯定不会有线程竞争,也可在JIT编译阶段安全消除对该变量实施的同步代码。
比如代码的lock方法消除。
配置:
-XX:+DoEscapeAnalysis 开启逃逸分析
-XX:+EliminateAllocations 开启标量替换。-表示关闭。
+XX:+EliminateLocks 开启同步消除
-XX:+PrintEliminateAllocations 查看标量的替换情况

逃逸分析的不足:

无法保证逃逸分析的收益大于进行此操作带来的性能消耗。

线上内存问题排查:

如果线程还存在,
    通过jstat查看gcutl的垃圾回收状态,分年轻代minor gc不用管,关注老年代full gc。
    通过PrintGCDetails打印日志,利用工具GCViewer查看各个内存区域的情况。
    通过命令jmap -histo:live 25085 | head -20查看object instance和bytes情况
    通过jmap -dum:format=b,live.file=test.hprof 进程号导出堆栈文件
    通过JvisualVM软件导出文件
如果线程已退出,比如内存占用满抛出error,在启动参数配置-XX:+HeapDumpOnOutOfMemoryError,导出内存堆栈文件,用MAT(基于eclipse)或者esay.gc.io网站/IBM HeapAnalyzer/Jprofile分析。
    查看object list排行,确认对象是否都是必要的,先分清楚是内存泄露还是内存溢出。
    内存泄露:
        进一步通过工具如Jprofile查看泄露对象到GC Roots的引用链(show paths to GC ROOT),确认为什么不会被自动回收。
    内存溢出:
        调大参数或者优化代码。

线上CPU占用过高:

top -H -p <pid>  查看进程内各个线程的资源占用,通过jstack命令导出线程dump信息。把线程pid(对应线程dump内容中的nid,nid是16进制表示的)转换成十六进制,然后在线程dump文件中搜索即可找到对应的线程栈方法执行情况。
    jstack [ option ] pid | grep 十六进制线程 -A60
        -l长列表. 打印关于锁的附加信息
        -e打印额外信息
阿里开源Arthas
posted @ 2021-12-14 16:45  心平万物顺  阅读(26)  评论(0编辑  收藏  举报