Java入门学习笔记

本文详细介绍了Java的基础知识,包括数据类型、引用数据类型、类型转换、变量、运算符、循环结构、方法调用、构造方法、数组、集合、Map、线程、并发容器、反射、IO、数据库、Spring框架、JVM内存模型以及并发基础等内容。同时讲解了面向对象的封装、继承、多态,以及JVM的内存管理、GC机制。此外,还涉及了数据库的索引、缓存、分布式锁等概念。
摘要由CSDN通过智能技术生成

Java基础

本文章是作者的学习笔记,帮助初学者快速入门,内容讲的不是很细,适合初学者,不定时更新。

目录

❤🧡💛💚💙💜🤎🖤🤍💔❣💕💞💓💗💖💘💝💟 🫀👫💑💏💋♥😍😘😻🏩💒💌დღ♡❣❤❥❦❧♥

数据类型

1.基本类型(primitive type)

1-1 整数类型

byte占一个字节 short占两个字节

int占四个字节⭐常用

long占八个字节(long类型要在数字后面加L)

1-2 浮点类型

(1)float占个字节(float类型要在数字后面加F)

(2)浮点数是约莫类型(x过大时,结果无限接近但不等于x)

因此用float和double比较,会出现错误。

⭐(1)银行运算不用浮点数,而使用BigDecimal等(数学工具类)

double占个字节⭐常用

1-3 字符类型

char占两个字节

1-4 boolean类型

true 和 false

2.引用数据类型

⭐==和equals的区别

String str1 = "hello";//常量池,String是用final修饰的常量类型
String str2 = new String("hello");//实例化的String对象,在堆内存,引用在栈内存
String str3 = str2;//引用传递,传递的是引用地址(栈)
str1 == str2;//false
str1 == str3;//false
str2 == str3;//true
//equals本身和==没有区别,但是String类重写了equals方法,所以这个equals实际上是比较两个值的内容,地址不比

(1)类Class

(2)接口interface

(3)数组引用

3.类型转换

3-1 内存溢出和强制转换

(1)byte的取值范围:-128~127

int i = 128;

⭐byte b = (byte)i;//此处内存溢出,变成-128

(2)(类型)+变量名=强转(高到低)

3-2 自动类型转换

低到高的转换他会自动转换

⭐转换优先级:

低 ------------------------------------------- 高

byte,short,char→int→long→float→double

⭐注意:(1)不能对布尔值进行转换

			(2)不能把对象转换为不相干的类型

			(3)精度丢失(转换时默认向0取整)

			比如:(int)23.7 = 23     (int)-45.89f = -45

变量

1.类变量(静态变量)

static关键词(全局的,静态的)

2.实例变量

(1)从属于对象,使用需要实例化类

(2)如果不进行初始化,则它的值为这个类型的默认值(0,0.0)

3.局部变量

(1)必须声明和初始化值

4.常量

定义:常量是一种特殊的变量,初始化(initialize)之后不会变动

final 常量名 = 值;

运算符

(1)基本运算符(略)

(2)自增自减(略)

(3)位运算符(略)

(4)⭐三元运算符

x ? y : z

如果x==true,则结果为y,否则结果为z

顺序结构与循环

1-1 if选择结构

1-2 Switch选择结构

(2)switch高级应用

多条件

switch(grade){

case 'A':

case 'B':

System.out.println("shit");

break;

default:

System.out.println("什么都没有")

}

1-3 While循环

while(i<=100){
	//计算1+2+3+...+100
	sum+=i;
    i++;

}

do-while循环

⭐区别:do-while至少执行一次,因为他是先执行后判断。

1-4 For循环详解

for(初始化;布尔表达式;更新){

	//代码语句

}
(0)增强for循环

for(局部变量:访问数组名){

}

for(int a;aList){
    System.out.println("a="+a)
}
(1)break

强行退出此次循环。

(2)continue

只用于循环语句中。

用于终止某一次循环过程,直接进行下一次循环。

(3)goto

在break或者continue后加label,不要求掌握,比较麻烦

方法

1-1 可变参数

在定义方法时,在最后一个形参后加上三点 就表示该形参可以接受多个参数值,多个参数值被当成数组传入。上述定义有几个要点需要注意:

  • 可变参数只能作为函数的最后一个参数,但其前面可以有也可以没有任何其他参数
  • 由于可变参数必须是最后一个参数,所以一个函数最多只能有一个可变参数
  • Java的可变参数,会被编译器转型为一个数组
  • 变长参数在编译为字节码后,在方法签名中就是以数组形态出现的。这两个方法的签名是一致的,不能作为方法的重载。如果同时出现,是不能编译通过的。可变参数可以兼容数组,反之则不成立

⭐简单来说,你就把他看成数组就行了

1-2 递归

递归包含以下两个部分

⭐递归头:表示什么时候不调用自身方法。如果没有头就会死循环

⭐递归体:表示什么时候需要调用自身方法

public static void main(String[] args){
	System.out.println(f(4));
}
public static int fuck(int n){
    if(n==1){//递归体,表示什么时候需要递归
        return 1;
    }else{//递归头,也就是递归的结束条件
        return n*f(n-1);
    }
}

1-3 异常

https://www.runoob.com/java/java-exceptions.html

1-4 方法的调用

静态方法隶属于类,非静态方法隶属于对象

1-4-1 静态方法

调用形式:类名.方法名

1-4-2 非静态方法

先实例化

Student student = new Student

调用形式:对象名.方法名

1-4-3 形参

形参不同于实参,他只是一个做事的临时工具,而并不是这件事的组成部分。

↓↓↓↓↓↓↓↓↓↓↓

1-4-4 引用传递

当形参是一个对象的时候,传参传递的是引用的地址

public class Demo{
	public static void main(String[] args){
    	Person person = new Person();
    	System.out.println(person.name);//null
    	Demo.change(person);
        //调用同类型方法,需要用类名.方法名
        //因为都是静态方法可以直接调用,非静态方法需要new
        System.out.println(person.name);//sb
    }
    public static void change(Person person){
        //person是一个对象:指向的↓
        //Person person = new Person();这是一个具体的人,可以
        person.name = "sb";
    }
}
//定义了一个Person类,有一个属性:name
class Person{
    String name;//null
}

1-5 构造方法

在类创建的时候默认会创建一个构造方法

特点:1.必须和类名相同

		2.必须没有返回类型,也不能写void
Person p = new Person();//Person()就是构造方法

作用:1.实例化初始值

public Person()//无参构造
	this.name = "liubei";
}
		2.使用new关键字,必须要有构造器
public Person(String name){//有参构造
	this.name = name;//this.name是对象的name。后面的name是传的形参。
}

⭐注意:一旦定义了有参,无参也必须显示定义

数组

1-1 数组的创建方式

dataType[ ] arrayRefVar = new dataType[ arraySize ];//⭐常用

dataType arrayRefVar[ ];

1-2 数组的遍历方式

(1)for-each循环

int[] arrays = {1,2,3,4,5};
for(int array:arrays){//这里的数组不需要加[]
    System.out.println(array);//但这里没有带下标
}
//如果我们要打印数组的元素则用下面的方法
public static void printArray(int[] arrays){
    for(int i=0;i<arrays.length;i++){
        System.out.print(array[i]+"");
    }
}

1-3 冒泡排序

public static void main(String[] args){
    
}
public static int[] pao(int[] array){
    //int临时变量
    int temp = 0;
    for(int i=0;i<array.length();i++){
        for(int j=0;j<array.length()-1-i;j++){
            if(array[j]<array[j+1]){
                temp=array[j];
                array[j]=array[j+1];
                array[j+1]=temp;
            }
        }
    }
}

冒泡排序的时间复杂度为O(n2);//这里的n2指两次有关长度的操作

1-4 稀疏数组

处理方式:

	1.记录数组一共有几行几列,有多少个不同的值

	2.把具有不同值的元素和行列及值记录在一个小规模的数组中

面向对象

(OOP)Object-Oriented Programming

面向过程思想:

	1.步骤清晰简单,第一步干啥,第二步干啥

	2.面对过程适合处理一些简单的问题,比如烧水..

⭐面向对象思想:

	1.物以类聚,**分类** 的思维模式。各部门分工合作,大块分小块。

	2.面向对象适合处理复杂的问题,适合处理需要多人合作的问题。

⭐本质

面向对象编程的本质就是:以类的方式组织代码,以对象的组织(封装)数据。

高内聚,低耦合。

创建对象内存分析:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jzze5tb6-1631855103577)(https://edu-10121.oss-cn-beijing.aliyuncs.com/MyJavaStudyImg/A](D_T((E6)]6GCM`[TG53%4.png)

栈相当于目录,堆相当于内容。每当你new一个对象,都是属于“目录”某个章节的一页。比如你new一个“男人”,就是“人”这一章节的其中一页。对象是用过引用来操作的,由栈指向堆

1-1封装

重点:属性私有化。get、set

1-2继承

extends 子类继承父类的所有属性方法,但是私有属性方法无法被子类访问

减少重复代码,维护性提高,提高代码复用性,代码更简洁

继承的缺点:提高了耦合性,面向对象要求低耦合

1-2-1 super关键字

super只能出现在子类的方法或者构造方法中。

super.属性名/方法名 (用来调用父类的属性或方法

子类默认会调用父类的无参构造函数super()😭在第一行调用)

如果没有无参构造调用有参构造也可以。

⭐注意:不能与this同时用来调用构造方法

1-3多态

Student s1 = new Student();
Person s2 = new Student();
Object s3 = new Student();
//引用类型可以是父类,实际类型是确定的 new Student

⭐多态是方法的多态,属性和变量没有多态

⭐简单来说,静态看左,非静态看右(静态方法不能被重写)

原理:向上转型,里式代换原则(LSP)dd

ps:向下转型则是强制转换: 子类 子类的对象 = (子类)父类的对象

1-3-1 方法重写

⭐重点:父类的引用指向子类的对象

示例:1个行为,不同的对象,他们具体体现出来的方式不一样,
比如: 方法重载 overloading 以及 方法重写(覆盖)override
class Human{
void run(){输出 人在跑}
}
class Man extends Human{
void run(){输出 男人在跑}
}
这个时候,同是跑,不同的对象,不一样(这个是方法覆盖的例子)
class Test{
void out(String str){输出 str}
void out(int i){输出 i}
}
这个例子是方法重载,方法名相同,参数表不同

          ok,明白了这些还不够,还用人在跑举例 
          Human ahuman=new Man(); 
          这样我等于实例化了一个Man的对象,并声明了一个Human的引用,让它去指向Man这个对象 
          意思是说,把 Man这个对象当 Human看了. 

          比如去动物园,你看见了一个动物,不知道它是什么, "这是什么动物? " "这是大熊猫! " 
          这2句话,就是最好的证明,因为不知道它是大熊猫,但知道它的父类是动物,所以, 
          这个大熊猫对象,你把它当成其父类 动物看,这样子合情合理. 

          这种方式下要注意 new Man();的确实例化了Man对象,所以 ahuman.run()这个方法 输出的  是 "男人在跑 " 

          如果在子类 Man下你 写了一些它独有的方法 比如 eat(),而Human没有这个方法, 在调用eat方法时,一定要注意 强制类型转换 ((Man)ahuman).eat(),这样才可以... 对接口来说,情况是类似的... 

1-4 抽象

⭐抽象类:只有方法的名字,没有方法的实现。

继承了抽象类的子类,都必须实现抽象类的所有方法。

1-5 接口

⭐接口(Interface)只有规范

接口中所有的方法都是**公共(public)抽象(abstract)**的

只写返回值类型和方法名就行了。

可以实现接口

1.implements关键字(实现)

2.接口可以多继承(extends)

3.实现了接口的类,需要实现接口中的所有方法。

1-6 内部类

public class hh{
 int a = 0;
 class int xx{
  System.out.println(a);//内部类可以用外部的属性和方法
 }
}

集合

⭐hashCode与equals的区别:

hashCode相等,对象值不一定相等

数组存在很多缺点,比如:

1.长度开始时必须指定,而且一旦指定不能更改

2.保存的为同一类型的值 3.代码比较繁琐

于是,集合他来了

https://www.debugger.wiki/sourceimg/190617/92ae67a8310674dd6981e1a7b65b652d.jpg

Collection 接口的实现子类:单列集合,存放单个数据

Map 接口的实现子类: 双列集合,键值对形式

⭐Map的实现子类还有Hashtable,Properties是Hashtable的实现子类

⭐List的实现还有Vector

1-1 迭代器

Collection中的Iterator用于遍历集合

(1)hasNext()方法,判断后面是否还有元素。

(2)next()方法,下移并将下移之后集合位置上的元素返回。

⭐用next()之前必须用hasNext(),不然会抛出异常NoSuchElementException

while(iterator.hasNext()){
    Object obj = iterator.next();//下移并将下移之后集合位置上的元素返回。
    System.out.println(obj);//输出next()方法返回
}

1-2 List

  1. HashCode的特性

(1)HashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,HashCode经常用于确定对象的存储地址;

(2)如果两个对象相同, equals方法一定返回true,并且这两个对象的HashCode一定相同;

(3)两个对象的HashCode相同,并不一定表示两个对象就相同,即equals()不一定为true,只能说明这两个对象在一个散列存储结构中。

(4)如果对象的equals方法被重写,那么对象的HashCode也尽量重写。

本文原创,转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52094115

  1. HashCode作用

Java中的集合有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。 equals方法可用于保证元素不重复,但如果每增加一个元素就检查一次,若集合中现在已经有1000个元素,那么第1001个元素加入集合时,就要调用1000次equals方法。这显然会大大降低效率。 于是,Java采用了哈希表的原理。

哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。这样一来,当集合要添加新的元素时,先调用这个元素的HashCode方法,就一下子能定位到它应该放置的物理位置上。

(1)如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;

(2)如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了;

(3)不相同的话,也就是发生了Hash key相同导致冲突的情况,那么就在这个Hash key的地方产生一个链表,将所有产生相同HashCode的对象放到这个单链表上去,串在一起。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

int indexOf(obj);//返回obj在集合中第一次出现的位置(int)
1-2-1 ArrayList

是线程不安全的,可以看源码,没有synchronized,所以多线程不用ArrayList,可以考虑用Vector

底层结构:

(1) ArrayList中维护了一个Object类型的数组elementData.

transient Object[] elementData;//transient 表示瞬间的,短暂的。表示该属性不会被序列化。

(2) 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,扩容为10,如需要再次扩容,则扩容为1.5倍

//0->第一次10->第二次15->第三次22,以此类推

(3) 如果使用的是指定int大小的构造器,那么elementData初始容量为指定的int值,如果要扩容,则扩容为1.5倍

//你指定的int值->int*1.5

源码:

public boolean add(E e){//e:1
    //执行list.add,先确定是否要扩容
    ensureCapacityInternal(size + 1);
    //然后再执行赋值
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity){
    //用来确认最小容量
    //首次确认最小容量
    if(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA){
       minCapacity = Math.max(DEFAULT_CAPACITY,minCapacity);
       //比较默认值与最小值1,然后将其大值赋给minCapacity
    }
    ensureExplicitCapacity(minCapacity);//继续确认最小容量
}
private void ensureExplicitCapacity(int minCapacity){
    //确认完最小容量然后进行判断
    modCount++;//记录集合被修改的次数
    if(minCapacity - elementData.length>0)//判断容量够不够
        grow(minCapacity);//进行扩容,使用的copyof
}
1-2-2 Vector

Vector是线程安全的。它的方法带有synchronized

1-2-3 LinkedList

(1) LinkedList底层维护了一个双向链表

(2) LinkedList中维护了两个属性 firstlast 分别指向 首节点尾结点

(3) 每个结点里面又维护了三个属性prev,next,item,其中通过next指向后一个节点的地址,prev指向前一个地址。数据的添加不是通过数组完成,无需扩容,所以相对效率高

底层逻辑:

首先是新建一个Node结点,有三个属性。只有一个值得时候,prev和next都是null

只有item值并且first和last都指向这个Node结点。

有多个结点时,第一个加入的使它等于first,最后一个使它等于last,前后根据prev和next来指向地址,形成双向链表

添加
void linkLast(E e){//Node添加的底层
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l,e,null);
    //新建一个Node
    last = newNode;//管他三七二十一,先把最新的这个Node设为last
    if(l == null)//如果这个Node是第一个(null)那就把他设为first
        first = newNode;
    else
        l.next = newNode;//如果这个Node不是第一个,就吧上一个的next指向这个Node
    size++;//结点+1
    modCount++;//运行计数+1
}
删除
private E unlinkFirst(Node<E>f){//例子:删除第一个
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null;
    first = next;
    //此时原先的第一个结点就变成了孤儿,等待被垃圾回收!
    if(next == null)
        last = null;
    else
        next.prev = null;
    size--;//结点-1
    modCount++;//运行计数+1
    return element;
}
1-2-4 List集合选择

1-3 Set

1.set接口对象(接口的实现类的对象),不能存放重复的元素,可以放null

2.set接口对象存放数据是无序的

3.取出对象的顺序虽然不是添加的顺序,但是他是固定的,不会存在第一次第二次取出不一样顺序的情况

1-3-1 HashSet

add()方法执行时会返回一个boolean

set.add("liubei");//true
set.add("liubei");//false,常量池重复
set.add(new Dog("wangcai"));//true
set.add(new Dog("wangcai"));//true,开辟一个新地址,HashCode不一样
⭐面试题
set.add(new String("lsp"));//true
set.add(new String("lsp"));//false
/*以这种方式赋值时,JVM会先从字符串实例池中查询是否存在"lsp"这个对象,

若不存在则会在实例池中创建"lsp"对象,同时在堆中创建"lsp"这个对象,然后将堆中的这个对象的地址返回赋给引用str。

若实例池存在则直接在堆中创建"test"这个对象,然后将堆中的这个对象的地址返回赋给引用str。*/


底层:HashMap,而HashMap的底层是(数组+链表+红黑树)

为什么相同的元素就加不了呢?

他有一个hash()和equals()两个方法,用来比较相同的元素,两个方法都成立时,则不允许你的元素添加。

源码:

1.add

(1)执行hashSet()
public HashSet(){
map = new HashMap<>()
}
(2)执行add()
public boolean add(E e){//E是泛型,e是我们输入的对象
    return map.put(e,PRESENT)==null;//PRESENT是固定对象
}
(3)执行put(),该方法会执行hash(key)得到key对应的哈希值
public V put(K key,V value){
    return putVal()
}
(4)HahMap下面新建一个table数组(是Node[]类型的),然后将hash值,key(你输入的值),value(地址),next=null放入这个table对应的位置。

执行前面两个if语句,如果当前table是null或者大小为0.进行第一次扩容到16。

(5)添加失败的情况:

前面两个if语句是false,然后执行else

final V putVal(int hash,K key,V value,boolean onlyIfAbsent,boolean evict){
	Node<K,V>[] tab;Node<K,V> p;int n, i;
    if((tab = table)==null||(n=tab.length)==0)
        n=(tab=resize()).length;
    if((p=tab[i=(n-1)&hash])==null)
        tab[i] = newNode(hash,key,value,null);
else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null &&      key.equals(k)  )))
                //这里的p指向数组链表第1个位置上的结点,如果这次要加的key对象和p指向的这个Node里面的key和hash值一样并且:(两个key是同一个对象或者equals返回true) 那就不能加入。
                e = p;
    //如果false再判断是不是红黑树,是就调用putTreeVal
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    //和对应那个链表的每个值比较,如果没有这个值就丢在链表
    //在添加元素到链表之后,立即判断是否有八个结点,如果有八个就调用treeufyBin()对当前链表树化(树化过程有很多判断)
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }

1-4 Map

核心:key-value键值对

1.Map中的key和value可以是任何引用类型的数据,最终会封装到HashMap$Node对象中

HashMap$Node node = new Node(hash,key,value,null);

2.k-v为了方便程序员的遍历,还会创建EntrySet集合,该集合存放的元素类型是Entry,一个Entry包含k,v EntrySet<Entry<K,V>>

3.entrySet中,定义的类型是Map.Entry,但是实际上存放的是HashMap$Node.

为什么?因为HashMap$Node实现了Map.Entry接口,这就是接口的多态!!!

父类的引用(Map.Entry)指向子类的对象(HashMap$Node)

map.put("Number1","hh");
map.put("Number1","xx");
//输出xx,因为有相同的key时,value会替换

注意:Key可以为空,但只能有一个null,value也可以为null。常用String类作为key

⭐总结:HashMap里面有一个table一个entrySet集合,table用来装Node结点,而每个Node的key和value会被封装成entry对象放到entrySet集合中去,而entrySet看似存放的是Map.Entry,其实是HashMap N o d e 因为 H a s h M a p Node因为HashMap Node因为HashMapNode实现了Map.Entry,存的东西说白了就是table中Node结点的引用对象。

1-4-1 HashMap

HashMap的扩容机制:

1.第一次添加,需要扩容为16,临界值为12(16*0.75=12)

2.以后在扩容,扩容table为原来的2倍,临界值为之前的2倍,24,以此类推

3.java8以后如果一跳链表元素超过TREEIFY_THRESHOLD(默认8),并且table大小大于等于MIN_TREEIFY_CAPACITY(默认64),就会树化增加效率。

1-4-2 HashMap和HashTable的区别和底层实现

HashMap线程不安全,HashTable线程安全。HashMap允许k和v为空,HashTable不许。

HashTable相当于给所有方法加了synchronized关键字,效率低,于是乎我们现在一般用ConcurrentHashMap

底层实现:

数组+链表+红黑树

JDK8开始链表高度到8,数组长度超过64,链表转化为红黑树,元素以内部类结点Node形式存在。

(1)计算key的hash值,然后二次hash对数组长度取模,然后给他在数组安排位置,没有hash冲突,就直接将数据存为一个Node

(2)如果产生冲突,则进行equals比较,相同则取代,不同,则判断链表高度插入链表,链表高度达到8,并且数组64则变为红黑树,长度低于6又会变回链表

(3)扩容因子:为什么是0.75?这里回答一下取其他值的缺点就行了:

如果是0.5 ,每次水加到一半就增加杯子的大小,那杯子的空间利用率肯定会越来越小。 如果是1,每次水装满再增加杯子的大小,那肯定会浪费时间(装满了就不能装了,只能等你先增加杯子的大小)

泛型

相当于给你限定一个数据类型E,这个E可以是String,int等,一旦限定,这个参数以后传的就只能是你限定的类型

反射

1-1 反射入门方法

反射(java.lang.reflect)就是还没有new的时候,就可以使用这个类,相当于一个镜子
从这个简单的例子可以看出,一般情况下我们使用反射获取一个对象的步骤:

1.获取类的 Class 对象实例

Class clz = Class.forName("com.zhenai.api.Apple");//Class对象(java.lang.Class)表示某个类加载后在堆中的对象,就是这个com.zhenai.api.Apple

2.根据 Class 对象实例获取 Constructor 对象

Constructor appleConstructor = clz.getConstructor();

3.使用 Constructor 对象的 newInstance 方法获取反射类对象

Object appleObj = appleConstructor.newInstance();

4.而如果要调用某一个方法,则需要经过下面的步骤

获取方法的 Method 对象

Method setPriceMethod = clz.getMethod("setPrice", int.class);

利用 invoke 方法调用方法

setPriceMethod.invoke(appleObj, 14);

1-2 优点和缺点

反射的优点:

可以动态创建和使用对象(也是框架底层核心),使用灵活

反射的缺点:

使用反射基本是解释执行而不是编译执行,对执行速度有影响.(编译快)

//传统方法调用方法
public void hh(){
    Man man = new Man();
    long start = System.currentTimeMillis();
    for(int i=0;i<100000;i++){
        Man.fuck();
    }
    long end = System.currentTimeMillis();
    System.out.println(end-start);
}
//反射的方法
public void hh2() throws Exception{
    Class cls = class.forName("com.yc.Man");
    Object o = cls.newInstance();
    Method fuck = cls.getMethod("fuck");
    long start = System.currentTimeMillis();
    for(int i=0;i<100000;i++){
        fuck.invoke(o);
    }
    long end = System.currentTimeMillis();
    System.out.println(end-start);
}
//很明显,普通方法0ms,反射需要几百到几千ms
//如何优化?用setAccessible禁用访问安全检查,速度就会快很多
fuck.setAccessible(true);
fuck.invoke(o);//true表示禁用

1-3 Class 类

//Class类对象不是new出来的,而是系统创建的
不管是传统方法还是反射方式
    都是通过ClassLoader来加载类的Class对象
//某个类的Class类对象,在内存中只有一个,因为类只加载一次,这个对象存在堆
1-3-1 获取Class的几种方式

1.配置文件,读取类全路径,加载类

Class cls1 = Class.forName("java.lang.xx");

2.参数传递,用反射获得构造器等

Class cls2 = xx.class;

3.通过创建好的对象,获取Class对象

XX xx = new XX();
Class cls3 = xx.getClass();

4.其他

四种类加载器去JVM看

1-4 动态和静态加载

1.静态加载(普通):编译时就加载相关的类,如果没有则报错,依赖性强

2.动态加载(反射):运行时并执行到相应代码的时候才加载相关的类

switch(key){
        case"1":
        Dog dog = new Dog();
        dog.cry();
        break;
        //静态加载
        //你只要运行就会报错,因为没有Dog这个类
        case"2";
        	Class cls = Class.forName("Person");
            Object o = cls.newInstance();
            Method m = cls.getMethod("hi");
            m.invoke(o);
            break;
        //动态加载
        //除非你代码走到case"2"不然不会报错
    default:       
}

1-5 获取类的结构信息

1-6 通过反射爆破创建对象

1.调用类中的public修饰的无参构造器

2.调用类中的指定构造器

3.Class类相关方法

newInstance:调用类中的无参构造器,获取对应类的对象

getConstructor:根据参数列表,获取对应public构造器的对象

getDecalaredConstructor:根据参数列表,获取所有对应构造器对象

⭐4.Constructor类相关方法

⭐setAccessible:爆破

Constructor<?> c1 = userClass.getDecalaredConstructor(int.class,String.class);
c1.setAccessible(true);//爆破,暴力破解使反射可以访问private构造器
Object user2 = c1.newInstance(100,"张三丰");

newInstance(Object…obj):调用构造器

动态代理

IO

文件:保存数据的地方

File file = new File(parentFile,fileName);
//相当于在内存创建了一个对象
file.createNewFile();
//真正把对象创建到硬盘中

1-1 IO流原理

输入input: 读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。

输出output: 将程序(内存)数据输出到磁盘、光盘等存储设备中。

1-2 流的分类

抽象基类 字节流 字符流
输入流 InputStream Reader
输出流 OutputStream Writer
1-2-1 InputStream

read()方法用来输入

1-2-2 OutputStream

write()方法用来输出

FileOutputStream f = null;
try{
f = new FileOutputStream(filePath);
    // = new FileOutputStream(filePath,true);这种方式会追加到文件后面而不是覆盖文件
String str="你是个渣渣";
f.write(str.getBytes(),0,3);//getBytes可以将字符串转换为字符数组
    }
catch(IOException e){
    
}

1-3 文件拷贝

//关键代码
 String firstPath = "e:\\1.png";
	        String lastPath = "d:\\2.png";
	        FileInputStream fileInputStream = null;
	        FileOutputStream fileOutputStream = null;

fileInputStream = new FileInputStream(firstPath);
				fileOutputStream = new FileOutputStream(lastPath);
				//定义一个字节数组,提高效率
				byte[] buf = new byte[1024];
				int readLen = 0;
				while ((readLen = fileInputStream.read(buf))!= -1) {
					fileOutputStream.write(buf);//一定要写这个方法
				}
				System.out.println("拷贝完毕");

if(fileInputStream==null) {
						fileInputStream.close();
					}
					if(fileOutputStream==null) {
						fileOutputStream.close();
					}

1-4 文件字符流

输入流和字节流一样的玩法

1-5 节点流/处理流

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xmGbevhk-1631855103602)(…/Images/java/8.png)]

1-5-1 BufferedReader

BufferedReader中,有属性Reader,即可以封装一个节点流,该节点流可以是任意。只要是Reader的子类。

public class BufferedReader extends Reader{
    private Reader in;
    private char cb[];
}
1-5-2 两者关系

1.节点流是底层流/直接根数据源相连

2.处理流包装节点流,即可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出

3.处理流对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连

1-6 BufferedReader

String filePath = "e:\\a.java";
//创建bufferedReader
BufferedReader br = new BufferedReader(new FileReader(filePath));
//读取
String line;//按行读取,效率高
//readLine()是按行读取,当返回null时,表示文件读取完毕
while((line = bufferedReader.readLine())!=null){
    System.out.println(line);
}
//关闭流,这里注意,只需要关闭br,因为底层会自动关闭节点流
bufferedReader.close();

数据库

Redis

1.Nosql

刚开始所有机子都是用的单机mysql

后来为了减轻服务器压力开始改变!

Nosql = Not Only SQL,redis是其中发展最好的

1-1 索引

索引是帮助MySql高效获取数据的排好序数据结构

1-1-1.引入缓存

Memcached(缓存)+垂直拆分+MYSQL

1-2-1.分库分表+水平拆分+MYSQL集群

有些不会改动的数据,把他放到A集群数据库中,只需要读一次,给他存到缓存里面,要改的东西放在B,C等集群数据库中。

MYISAM存储引擎索引实现:表锁,每次查东西要锁一个表,效率很低

Innodb存储引擎索引实现:行锁,效率问题解决了

数据库不同的引擎,存储的数据库表文件形式不同

以innoDB引擎举例:

test.frm文件存表结构 & innoDB.ibd文件存数据和索引文件

innoDB(聚集索引,因为他的数据和索引都存在ibd文件中,而其他引擎是分开存的,所以他遍历最方便)的存储结构就是B+树

为什么innoDB表必须有主键,并且推荐自增

B+树(多叉平衡树)

(1)非叶子节点不存储data,只存储索引(冗余),可以放更多的索引

(2)叶子节点包含所有索引字段

(3)叶子节点用指针连接,提高区间访问的性能

B+树的叶子节点用指针连接,提高区间访问的性能

我们知道HashMap的底层在最新的jdk里面加入了红黑树,为什么我们数据库不用红黑树?

首先,红黑树(根是黑色,叶子是黑色,每个红色节点必须有两个黑色子节点)的底层数据结构是一种自平衡二叉查找树,典型的用途是实现关联数组,对于插入密集型使用红黑树,对于查找密集型,使用AVL树

2.Nosql四大分类

KV键值对:

*新浪:redis

*美团:redis+tair

*阿里:redis+memecache

文档型数据库(bson格式,和json一样):

(1)MongoDB(一般必须要掌握)

是一个基于分布式文件存储的数据库,由C++编写,用来处理大量的文档

是一个介于关系型数据库和非关系型数据库的中间产品,它是非关系型数据库中功能最丰富,最像关系型数据库的。

3.redis基本数据类型

redis(Remote Dictionary Server),远程字典服务

redis默认16个数据库(0~15),默认使用第0个

select x//切换为第x个数据库
keys * //查看所有key值
flushdb//删库,别乱用
set 变量名 变量值//
exists name//检查name是否存在
move name//移出
expire name time//数据存放time秒
type name//查看name的类型
    incr name//使name+1
    decr name//使name-1

🧡redis是单线程的,因为它基于内存操作,cpu不是redis性能瓶颈,redis的瓶颈是内存和网络带宽。

redis是c语言写的,官方提供的数据为十万+的QPS(每秒查询率),比Memecache还厉害

String类型
# setex //(set with expire)设置过期时间
# setnx//(set if not exist)不存在则设置,可用于分布式锁
    #msetnx //是一个原子性操作,要么一起成功要么一起失败
    # getset xx yy//如果不存在xx则返回nil然后创一个xx值为yy
    //如果存在值,则返回原来的值,并设置新值yy
List类型
# LPUSH list one//LPUSH 将一个值或多个值插入列表list头部
# RPUSH//插入在list尾部    
    #LRANGE list 0 -1//查询list中0位到-1位的值
    #LINDEX list 0//获取list中某一位的值

两边插入效率高,中间效率会低一点

Set类型

和List一样,命令以S开头

Hash类型

就是Map集合,key-value,命令以H开头

Zset有序集合

在Set集合的基础上,增加了一个值

 

Zset有多种排序的方法

4.redis特殊数据类型

geospatial 地理位置

看地理位置的

5.事务

事务的本质:是一组命令的集合。一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行。

编译型异常(代码有问题,命令有错),事务中所有命令都不执行(事务单个命令具有原子性,事务不保证原子性,因为事务异常发生后没有回滚!)

运行时异常(命令语法错误)

acid

一次性,顺序性,排他性

127.0.0.1:6379>multi #multi开启事务
ok
127.0.0.1:6379>set k1 v1#向事务加入语句
QUEUED
127.0.0.1:6379>set k2 v2
QUEUED
127.0.0.1:6379>set k3 v3
QUEUED
127.0.0.1:6379>exec #执行事务
127.0.0.1:6379>DISCARD #取消事务,事务队列中的语句都不会执行
127.0.0.1:6379>watch money #监视money对象
Jedis

以前用的

Lettce

现在用的

Java高级

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2o26uv2d-1644913012215)(https://segmentfault.com/img/remote/1460000022180990)]

Spring

在这里插入图片描述

1.IOC/DI/AOP

(1)控制反转IOC

在这里插入图片描述

(2)依赖注入DI

在这里插入图片描述
步骤1:

UserService userService = new UserService;

for(Field field : userService.getClass().getDeclaredFields()){//遍历获取对象所有的字段
	if(field.isAnnotationPresent(Autowired.class)){//判断遍历的对象是否有自动注入关键字
	field.set(userService1,??);//如果有则注入值
	}
}

那么,问题就来了,这些对象是怎么创建的呢?答案是spring用ApplicationContext.getBean为我们创建的,是注入了值的。(有值的称为bean对象了,他是在实例化后将bean的引用和值注入到bean的属性中)

而简单利用构造器无参构造是没值的
在这里插入图片描述
依赖注入之后,把bean对象放入单例池Map中,然后getBean就可以用了,上面因为""里面的名字一样,相当于调用Map里面的同一个对象userService。
源码
在这里插入图片描述

JVM虚拟机

🧡面试题:JDK,JRE,JVM的区别?
JDK:java开发工具:包括JRE和java工具(工具里面有javac)
JRE:java运行时环境 包括jvm和api(api用来解释.class文件)
JVM:java虚拟机(包含在JRE中)
在这里插入图片描述

1-1 JVM的位置

JVM包括在JRE中,在操作系统之上。

1-2 类加载

1.加载Loading

在该阶段主要目的是将字节码从不同的数据源(可能是class文件,jar包等)转化为 二进制字节流加载到内存中 ,并生成一个代表该类的java.lang.Class对象

2.连接Linking

(1)验证阶段:

为了确保Class文件中的字节流包含的信息符合当前虚拟机要求,有元数据验证等

(2)准备阶段:

JVM在该阶段对静态变量,分配内存并默认初始化。这些变量所使用的内存都将在方法区中进行分配

class A{
    //n1是实例属性,不是静态变量,因此在准备阶段,不会分配内存
    //n2是静态变量,分配内存。n2默认为0,而不是20
    //n3是final常量,一旦赋值就不会变了,所以默认为30
    public int n1 = 10;
    public static int n2 = 20;
    public sattic final int n3 = 30;
}

(3)解析阶段:

虚拟机将常量池内的符号引用替换为直接引用的过程

a的具体地址指向new出来的堆内存地址

3.Initialization初始化

(1)此阶段,才真正开始执行类中定义的java程序代码,此阶段是执行方法的过程。

(2)()方法:按照语句在文件中出现的顺序,收集整理赋值动作,并进行合并

static{
    int num=10;
    System.out.println(num);
}
class A{
    int num=20;
}
//输出num=20

(3)虚拟机会保证一个类的()方法在多线程环境中被正确的加锁,同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行完().

⭐可以考虑使用-Xverify:none参数来关闭大多类验证措施

以Car为例子

作用:加载Class文件,初始化生成相应Car Class(相当于一个工厂,用来制造Car类的)相应Class就可以开始实例化对象了

⭐new关键字,底层就是一个LoadClass方法

public Class<?> loadClass(String name)throws ClassNotFoundException{
    return loadClass(name,false);
}

⭐反射1-3Class类中的forName方法

对象要变回Class用getClass方法,Class要变回Class Loader用getClassLoader方法

引用在栈,实体在堆:栈存放的是实例,堆存放的是内存地址空间

人住在客栈,人的东西随便放一堆。

扩展:rt.jar是什么,为什么重要?

双亲委派机制(安全)

1.运行一个类之前,类加载器会收到类加载的请求。他会将这个请求向上查找(应用加载器-》扩展加载器-》根加载器)

最终执行最靠近上边的类(比如String类在根加载器,你用String类就是调用的根加载器里面的String,而不是你自己写的)

1-3 沙箱安全机制

(了解)

沙箱机制就是将java代码限定在JVM特定的运行范围中,并且严格限制代码对本地系统资源访问。(保证对代码的有效隔离,防止对本地系统造成破坏)

组成:

字节码校验器:校验字节码,在类加载过程中校验。

⭐扩展>>>>>>编译码校验:在类加载之前校验。

1-4 Native关键字

⭐多线程的start0!!

凡是带了native关键字的,说明java的库作用范围达不到了,就会去调用底层c语言的库

会进入本地方法栈,然后调用:

上面1-1的 本地方法接口(JNI:Java Native Interface)

而JNI的作用就是:扩展Java的使用,融合不同的编程语言(C,C++)为Java所用

所以说本地方法栈就是专门开辟用来调用(C,C++)其他语言的区域

⭐本地方法一般不用,用的最多的本地方法System.currentTimeMillis();

1-5 PC寄存器和方法区

1-5-1 PC寄存器

程序计数器:

每个线程都有一个程序计数器,是线程私有的,就是一个指向方法区中的方法字节码的指针,在执行引擎读取下一条指令

1-5-2 方法区

方法区是被所有线程共享的,所有定义的方法信息都保存在此

⭐静态变量,常量,类信息(构造方法,接口定义),运行时的常量池存在方法区中,但是实例变量存在于堆内存中,和方法区无关

⭐扩展:

元空间不等于方法区,是jdk1.8之后方法区的实现,之前的实现是永久带。字符串常量池以及运行时常量池逻辑上属于方法区,实则属于堆。

1-6 栈与队列

⭐栈:先进后出,对应的线程

队列:先进先出

栈内存:主管程序的运行,生命周期和线程同步(main算作主线程)

因此栈不存在垃圾回收问题(程序结束,栈就拜拜了)

⭐栈帧

Java中的栈帧随着方法调用而创建,随着方法结束销毁,可以理解为分配给方法的一块栈空间,每调用一个方法就创建一个栈帧。
因此,一般我们的main方法的栈帧都在栈底,由于栈是后进先出,所以你现在应该理解为什么main方法最后结束了吧!

因此main方法一般都在栈底,不然会报错StackOverflow

而栈满了也会报此错误

⭐栈主要存放:八大基本类型,引用数据类型等…

⭐为什么java数据要分类型?

引用类型在堆里,基本类型在栈里。

栈空间小且连续,往往会被放在缓存。引用类型cache miss率高且要多一次解引用。

对象还要再多储存一个对象头,对基本数据类型来说空间浪费率太高

1-7 堆!

Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的

存储的全部是对象,每个对象包含一个与之对应的class信息–class的目的是得到操作指令。

对象在内存中的存储布局分为三部分:

1.对象头
java里面用两个字来表示对象头,一个是Mark Word,一个是Klass pointer(Class MetaData Address)

(1)Mark Word(包含了自身运行时数据)
包括锁状态(lock,bkased_lock等),hashcode,GC分代年龄(age),线程持有的锁等… …

(2)Klass pointer(就是一个指针)
虚拟机通过这个指针来确定对象是哪个类的实例

64位虚拟机的对象组成(有锁无锁):
image-20210917143214445

2.实例数据
就是你在对象里面写的东西()

3.对齐填充
JVM要求对象起始地址必须是8字节的整数倍(8字节对齐),所以不够8字节就由这部分来补充。

OOM:堆内存错误

⭐堆的分区有哪些?分别是什么功能

堆内存分为三个区域:

  1. 新生代(Minor GC)新生代采取淘汰机制,活不下去的就被轻GC回收

    ​ a.伊甸园

    ​ b.幸存from区

    淘汰机制就是在from和to去进行言赢算法15次,活下来就可以走,为什么是15次?15=1111 刚好四个字节,存在对象头中

    ​ c.幸存to区

  2. 老年代(Full GC)

  3. 永久代(1.8后改为元空间)

    此区域常驻内存,用来存放JDK自身携带的Class对象,Interface元数据,此区域不存在垃圾回收,关闭JVM时永久代自动关闭

    1.6之前:永久代,常量池在方法区

    1.7:永久代,常量池在堆中

    1.8:无永久代

⭐堆内存调优:

jvm分配的堆内存是电脑内存的1/4,初始堆内存是1/64

VM options: -Xms1024m -Xmx1024m -XX:+PrintGCDetails
    //Xms最小内存 Xmx最大内存

堆内存=原生代+老年代的内存

1-7-1 堆内存的组成(代码演示)
//先在pom.xml里面引入jol依赖,使得我们可以直观看到堆对象的组成
<dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
</dependency>
public class A{
	private String name;
	private int age;
}
import org.openjdk.jol.info.ClassLayout;

public class Test {
    static A a = new A();

    public static void main(String[] args) {
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

输出演示:

请添加图片描述

1-8 GC!

垃圾回收机制

⭐GC的算法有哪些?

1.复制算法:

2.标记整体算法:

3.标记清除算法:

4.分代收集算法:

⭐轻GC和重GC分别在什么时候发生

线程

线程是由进程创建的一个实体,线程可以继续创建线程

1.并发:同一个时刻,多个任务交替执行,造成一种“好像是同时”的错觉,单核cpu中多任务就是并发

2.并行:同一时刻,多个任务同时运行,多核cpu可以并行

1-1 创建线程的方式

(1)继承Thread类(Thread类已经实现了Runnable接口),重写run方法

class T1 extends Thread{
    run(){
    }
}

(2)实现Runnable接口,重写run方法 (run方法是Runnable接口的,Thread是实现的Runnable的run方法)

注意:run()里面写你自己的业务逻辑

(3)用start()方法启动线程,因为run()仅仅只是一个方法,无法达成并行只能串行。启动后不一定马上执行要等待cpu

public synchronized void start(){
    start0();//实现多线程效果的是他,而不是run这是个jvm控制的native方法
}

⭐注意:不论是继承Thread还是实现Runnable接口,本质上都是调用start()里面的start0()方法!

1-2 线程常用方法

setName,getName,start,run,setPriority更改优先级,getPriority获取优先级

interrupt,中断线程,实际上用来唤醒正在睡觉的线程

sleep,线程中的静态方法,使线程休眠

// yield : 线程礼让方法,让其他方法先执行,但不一定成功,要看cpu调度
// join  : 线程插队方法
Thread t1 = new Thread();
Thread t2 = new Thread();
t2.join();//t1的时候t2调join,t2先插队完全执行完毕,然后执行t1


1-3 线程的种类

用户线程:也叫工作线程,当线程的任务执行完或通知方式结束

守护线程:为工作线程服务,当所有用户线程结束,守护线程自动结束

t1.setDaemon(true);//将t1设置为守护线程,用户线程结束它就结束

常见的守护线程:垃圾回收机制

1-4 线程的生命周期

1-6 死锁

多线程/高并发

1.并发基础

1-1 互斥同步
1-2 非阻塞同步
1-3 指令重排
1-4 synchronized(重量级锁)

synchronized相信大家都看过或者用过,synchronized是Java中的关键字,synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性,Java中每一个对象都可以作为锁,这是synchronized实现同步的基础。
synchronized 关键字可以保证原子性,也可以保证可见性

🧡它修饰的对象有以下几种:

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
  3. 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  4. 修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
    *synchronized使用详解 *

🧡synchronized的实现过程:
(1)Java代码中:synchronized关键字
(2)编译过程,字节码中:monitorenter(执行开始)moniterexit(执行完)
(3)JVM执行过程中:锁升级环节
(4)cpu中:lock cmpechg

1-5 volatile

使用volatile 关键字,可以强制的从公共内存中读取值。使用volatile关键字增加了实例变量在多个线程之间的可见性。但是volatile关键字的缺点是不支持原子性。
volatile 关键字可以禁止指令进行重排序优化,也可以增加可见性
🧡它修饰的对象是 变量

1-6 关键字的比较

1)volatile 是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,但是volatile只能修饰变量,而synchronized可以修饰方法,代码块。

2)多线程访问volatile 不会发生阻塞,而synchronized 会发生阻塞。

3)volatile 能保证数据的可见性,但不能保证原子性,而 synchronized可以保证原子性,也可以保证可见性,因为它会将私有内存和公共内存中的数据做同步。

2.锁

⭐锁的种类:

image-20210917143214445

new(啥都没有)---- 偏向锁 ---- 轻量级锁 ---- 重量级锁

第一次上锁为偏向锁,一旦有线程竞争就立马升级为轻量级锁,竞争激烈就变成重量级锁

轻量级锁:假设两个线程抢,先抢到的就把自己的Lock Record指针x贴到对象头上,后来的看看Lock Record都变成这个x,然后自旋,直到前面线程走了,后来的Lock Record就不等于这个x了,就可以把自己的贴上去了。(x抢到了厕所,y原地自旋,等x上完厕所)

重量级锁:用户态转内核态

JVM会检测到一直在给 str 这个对象重复加锁,于是会粗化,到while循环外面加锁,只需加一次了。

2-1 自旋锁

CAS: compare and swap(比较和交换)

重量级锁用的是用户态和内核态的频繁切换,导致重量级锁开销大,损耗高,而JVM的轻量级锁,用的是CAS进行自旋抢锁。
CAS具体操作:当前值和新值不相等就让其他线程改完再进行比较,直到相等的时候更新。
底层用的JNI里面的unsafe

image-20210917131244228

ABA问题:

你女朋友在分手状态时把你绿了,后面他们又分了,然后女朋友和你复合,你复合后不知道他这段时间和别人好了。这就是ABA

如何解决ABA:

离婚证(相当于标记一下)

⭐CAS在底层是通过

lock cmpxchg 指令实现的(这个指令和synconized和voliate有关)

2-2 偏向锁

因为经过HotSpot的作者大量的研究发现,大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁。

偏向锁的升级:

当线程1访问代码块并获取锁对象时,会在java对象头和栈帧中记录偏向的锁的threadID,因为偏向锁不会主动释放锁,因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致,如果一致(还是线程1获取锁对象),则无需使用CAS来加锁、解锁;如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。

偏向锁的取消:

偏向锁是默认开启的,而且开始时间一般是比应用程序启动慢几秒,如果不想有这个延迟,那么可以使用-XX:BiasedLockingStartUpDelay=0;

如果不想要偏向锁,那么可以通过-XX:-UseBiasedLocking = false来设置;

2-3 ReentrantLock可重入锁

在谈ReentrantLock 前,我们先来了解一下另一种锁分类:内置锁和显式锁,我们前面讲到的synchronized就是内置锁,很多小伙伴觉得synchronized已经很好用了啊,为什么还要搞一个什么显式锁呢?接下来就通过ReentrantLock来带大家了解:

有些事情内置锁是做不了的,我们只能搞一个新玩意ReentrantLock来做,比如:

(1)可定时:要加个超时等待时间,超时了就停止获取锁,这样就不会无限等待。

RenentrantLock.tryLock(long timeout, TimeUnit unit);

(2)可中断:通过外部线程发起中断信号,中断某些耗时的线程,唤醒等待线程。

RenentrantLock.lockInterruptibly();

(3)条件队列:
线程在获取锁之后,可能会由于等待某个条件发生而进入等待状态(内置锁通过Object.wait()方法,显式锁通过Condition.await()方法),进入等待状态的线程会挂起并自动释放锁,这些线程会被放入到条件队列当中。synchronized对应的只有一个条件队列,而ReentrantLock可以有多个条件队列

Condition.signal();
Condition.signalAll();//ReentrantLock通过这两种方法唤醒

注意:ReentrantLock 底层是AQS的实现,使用内置锁时,对象本身既是一把锁又是一个条件队列;使用显式锁时,RenentrantLock的对象是锁,条件队列通过RenentrantLock.newCondition()方法获取,多次调用该方法可以得到多个条件队列。

2-3-1 锁的操作

⭐锁升级
锁的4中状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态(级别从低到高)

⭐锁消除:

public void add(String str1,String str2){
    StringBuffer sb = new StringBuffer();
    sb.append(str1).append(str2);
}

我们都知道StringBuffer是线程安全的,因为它的关键方法都是被synchronized修饰过得,但我们看上面的代码可以发现,sb这个引用只会在add方法中使用,不可能被其他线程引用,因此sb是不可能共享的资源。JVM会自动消除StringBuffer对象内部的锁。

⭐锁粗化:

public String test(String str){
    int i=0;
    stringBuffer sb = new StringBuffer();
    while(i<100){
		sb.append(str);
        i++;
    }
    return sb.toString();
}

3.线程池

池化思想:线程池,字符串常量池,数据库连接池

这些都是为了提高资源的利用率

1.手动创建线程对象

2.执行任务

3.执行完毕,释放线程对象

4. 并发容器

5.JUC

5-1executor
5-2collections
5-3locks
5-4 atomic

原子类:许多都是native方法,详情查看1-4 native关键字

5-5 tools

数据结构与算法

1.线性结构和非线性结构

线性:元素之间存在一对一的线性关系,比如数组,队列,链表,栈

非线性:比如二维数组,多维数组,树结构,图结构

设计模式

1-1 单例模式

单例类一定要有并且只能有一个实例,并且只能自己创造实例。

1-1-1 饿汉式
public class Hungry{
    //饿汉式会浪费内存
    private Hungry(){
        //构造器私有
    }
    private final static Hungry h = new Hungry();
    public static Hungry getInstance(){
    //getInstance为创建一个唯一的实例,和new不同
		return h;
    }
}

1-2 工厂模式

实例化对象不用new,用工厂代替(买车要从车厂买)

(1)静态工厂模式

public static void main(String[] args){
    //Car car = new BenChi();普通方法new对象
    Car car = CarFactory.getCar("奔驰");
    /* 优点:不用考虑过程。缺点:违反了开闭原则,需要改进 */
}

(2)方法工厂模式:每个品牌一个类

(3)抽象工厂模式(强化版静态工厂模式,也就是工厂的工厂)

//手机产品接口
public interface IphoneProduct{
    void fuck();
    void suck();
}
//小米手机
public class XiaoMi implements IphoneProduct{
    public void fuck(){
        System.out.println("cnm");
    }
    public void suck(){
        System.out.println("gun");
    }
}
//工厂接口
public interface IProductFactory{
    //小米手机
    IphoneProduct iphoneProduct();
    //小米叽叽
    IJJProduct ijjProdcut();
}
//抽象工厂
public XiaomiFactory implements IProductFactory{
    public IphoneProduct iphoneProduct(){
		return new XiaoMi();
    }
}

1-3原型模式

其特点为复制,复制的对象就是【原型】
创建复杂或耗时的对象时,直接复制是最好的选择。

主流框架和项目管理

微服务概念

(1)微服务是一种架构风格

(2)有多个服务,多个服务独立运行,每个服务占用独立进程,比如我们的商城模块和oss模块。

(3)为什么需要微服务?因为我们之前的web项目是单体架构,所有的东西都耦合在一块,代码量大,维护困难,所以我们需要把一个单体拆分成多个独立运行的服务,每个服务有特有的功能

(4)微服务可以方便多数据源

1.spring cloud

spring cloud不是一种技术,是一系列框架技术的集合

spring cloud需要依赖spring boot 使用

2.Nacos注册中心

常见的注册中心还有ZooKeeper(搭配dubbo使用)

Eureka(springboot原生)

3.Feign(远程调用从这开始到Http Client结束)

Feign是网飞(Netflix)开发的声明式,模板化的http客户端,可以帮助我们快捷方便的使用html API

4.Hystrix

熔断器与服务雪崩:

服务雪崩是指B调用A,然后C和D调用B,这时如果A服务不可用了,那么会引起B不可用,然后C和D因为B不可用所以也不可用,这就叫服务雪崩。原因主要有:

1.硬件故障 2.程序bug 3.缓存击穿 4.用户大量请求

熔断器

开关关闭时,请求允许通过,如果健康状况高于阈值,开关保持关闭,如果低于阈值,开关打开,开关打开时,请求禁止通过,禁止一段时间后,只允许一个请求通过,这一个请求成功时熔断器恢复关闭状态。

5.Ribbon

对请求做负载均衡,比如生产者有一个集群,Ribbon会将这些请求平均分摊到不同的服务器中

Nginx

反向代理服务器,能实现很多功能:

1.请求转发:

​ 什么是请求转发?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q5cxhMfX-1644913012221)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\image-20211114094636893.png)]

2.负载均衡

image-20211114094948397

将客户端的请求,平均分摊到你的各个集群服务器中,达到负载均衡

3.动静分离

把java代码动态资源和页面文件那些静态资源分离开,根据请求申请动态或者静态资源

命令:

到文件夹打开cmd:

然后nginx.exe启动

nginx.exe -s stop关闭

RabbitMQ(中小型公司)卡夫卡(大型公司)

本质是个队列,先入先出,在系统高并发搞不定的时候,引入中间件。

主要功能有:

1-1流量消除峰值

在购买人数过多的时候,把超出的一部分延迟下单,虽然影响了用户体验,但是至少是能够下单而不会崩溃。

1-2

SSM

spring

springMVC

MyBatis

项目管理

Maven

Git

网络

1.TCP/IP

TCP可靠协议,传输数据时,会进行检查,没传输成功的他会重传,并且保证数据顺序不会乱!

UDP不可靠协议,他不检查,但是延时低,占用资源少

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员七海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值