java面试总笔记持续更新

java面试笔记)

一. java基础

1.11 hashcode的作用。

hashCode 是Java中Object类的一个方法,它返回对象的哈希码值。在Java中,哈希码用于支持哈希表数据结构,如HashMap、HashSet等。下面是hashCode的主要作用:

(1).在哈希表中查找对象: 哈希表是一种常用的数据结构,它使用哈希码来快速查找对象。hashCode方法的返回值可以被用作哈希表中的索引,帮助快速定位对象的存储位置。这样可以在常数时间内执行查找操作。

(2).在集合中确保对象的唯一性: 当你将对象添加到HashSet等集合中时,集合会使用hashCode来检查是否已经存在相同的对象。如果两个对象的哈希码相等,集合会进一步使用equals方法来比较对象的内容,以确保对象的唯一性。

(3).优化搜索性能: 在一些搜索算法中,哈希码可以用于快速排除不可能匹配的对象,从而提高搜索性能。这在大规模数据集的情况下特别有用。

(4).分布式系统中的一致性: 在分布式系统中,哈希码有时用于确定数据在不同节点之间的分布,以实现负载均衡和一致性哈希等策略。

需要注意的是,虽然hashCode方法在很多情况下是有用的,但是它并不是唯一确定对象相等性的标准。实际上,不同的对象可以有相同的哈希码(哈希冲突)。因此,在比较对象相等性时,还需要使用equals方法进行验证。在Java中,如果两个对象的hashCode相等,它们并不一定相等;而如果两个对象相等,它们的hashCode必须相等。

1.12.ArrayList和linkedList的区别.

ArrayList和LinkedList都是Java中常见的集合类,它们都实现了List接口,但它们在内部实现和性能方面有一些显著的区别。

底层数据结构:

ArrayList: 使用动态数组实现。它提供了随机访问元素的能力,因为它通过索引直接访问元素。

LinkedList: 使用双向链表实现。在链表中,每个元素都包含一个指向前一个和后一个元素的引用,这使得在列表中间插入和删除元素更为高效。

随机访问性能:

ArrayList: 由于底层是数组,因此可以通过索引直接访问元素,因此随机访问的性能较好。

LinkedList: 在链表中进行随机访问需要从头部或尾部开始遍历,因此随机访问的性能较差。时间复杂度为O(n),n是元素的索引位置。

插入和删除操作:

ArrayList: 在中间插入或删除元素时,需要移动元素来保持数组的顺序,因此这些操作可能较慢。在尾部进行添加或删除元素时效率较高。

LinkedList: 由于是双向链表,插入和删除元素的效率较高,特别是在链表中间进行这些操作。

空间占用:

ArrayList: 由于底层是数组,它通常比LinkedList占用更少的内存空间。

LinkedList: 每个元素都需要额外的空间来存储前后元素的引用,因此通常占用的内存空间较大。

迭代性能:

ArrayList: 由于可以直接访问元素,因此在迭代时效率较高。

LinkedList: 在迭代时需要通过引用一个个地遍历链表,因此相对较慢。

使用 ArrayList 当你需要快速随机访 问元素,而且对于添加和删除操作不太敏感。使用 LinkedList 当你需要频繁执行插入和删除操作,而对于随机访问不太敏感。

1.13.序列化与反序列化

(1)

序列化(Serialization):
序列化是将对象转换为字节流的过程。在Java中,可以通过实现 Serializable 接口来指示对象是可序列化的。实现该接口的对象可以被写入到输出流(如文件、网络流等),并在需要时通过反序列化重新构建对象。:

import java.io.*;

public class SerializationExample {
    public static void main(String[] args) {
        // 创建对象
        MyClass myObject = new MyClass(42, "Hello");

        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serialized_object.dat"))) {
            // 序列化对象
            oos.writeObject(myObject);
            System.out.println("Object serialized successfully.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// 实现 Serializable 接口
class MyClass implements Serializable {
    private int intValue;
    private String stringValue;

    public MyClass(int intValue, String stringValue) {
        this.intValue = intValue;
        this.stringValue = stringValue;
    }

    // 省略其他方法和构造函数的实现
}

(2)

反序列化(Deserialization):
反序列化是将字节流重新转换为对象的过程。同样,对象的类必须实现 Serializable 接口,以确保对象可以被正确地反序列化。

import java.io.*;

public class DeserializationExample {
    public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("serialized_object.dat"))) {
            // 反序列化对象
            MyClass myObject = (MyClass) ois.readObject();
            System.out.println("Object deserialized successfully.");

            // 使用反序列化得到的对象
            System.out.println("intValue: " + myObject.getIntValue());
            System.out.println("stringValue: " + myObject.getStringValue());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

1.14 浅拷贝和深拷贝

(1)浅拷贝
创建一个新对象,复制原对象的内容,但不复制对象内部引用的其他对象。
新旧对象内部的引用仍指向相同的实际对象。
修改新对象内部引用的对象会影响原对象。

class Person implements Cloneable {
    String name;
    Address address;

    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    // 浅拷贝
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

class Address {
    String city;

    public Address(String city) {
        this.city = city;
    }
}

public class ShallowCopyExample {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("CityA");
        Person originalPerson = new Person("John", address);

        // 浅拷贝
        Person clonedPerson = (Person) originalPerson.clone();

        // 修改克隆对象的城市
        clonedPerson.address.city = "CityB";

        // 原对象也受到影响,因为浅拷贝中地址引用是共享的
        System.out.println(originalPerson.address.city);  // 输出 CityB
    }
}

在这个例子中,Person类包含一个名为address的引用类型成员变量,该变量的类型是Address。当进行浅拷贝时,新对象和原对象共享相同的Address对象。因此,当修改克隆对象的Address时,原对象也会受到影响。这就是浅拷贝中所说的对象内部引用的其他对象。
(2)深拷贝

import java.io.*;

class Person implements Serializable {
    String name;
    Address address;

    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    // 深拷贝
    public Person deepCopy() throws IOException, ClassNotFoundException {
        // 将对象写入流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(bos);
        out.writeObject(this);

        // 从流中读取对象
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream in = new ObjectInputStream(bis);
        return (Person) in.readObject();
    }
}

class Address implements Serializable {
    String city;

    public Address(String city) {
        this.city = city;
    }
}

public class DeepCopyExample {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Address address = new Address("CityA");
        Person originalPerson = new Person("John", address);

        // 深拷贝
        Person clonedPerson = originalPerson.deepCopy();

        // 修改克隆对象的城市
        clonedPerson.address.city = "CityB";

        // 原对象不受影响,因为深拷贝创建了独立的对象副本
        System.out.println(originalPerson.address.city);  // 输出 CityA
    }
}

1.15 +和+=

您的理解是正确的。在Java中,+= 操作符会进行隐式的自动类型转换,而 + 操作符不会自动进行类型转换。

(1)对于第一个例子:


byte a = 127;
byte b = 127;
b = a + b; // 编译错误:cannot convert from int to byte
b += a;    // 正确,因为这里会进行类型转换
在 b = a + b; 中,a + b 的结果被认为是 int 类型,
所以需要进行强制转换才能赋值给 byte 类型的变量 b。
而在 b += a; 中,+= 操作符会对右边的表达式结果进行强制转换,因此不会报错。

(2)对于第二个例子:


short s1 = 1;
s1 = s1 + 1; // 编译错误:cannot convert from int to short
s1 += 1;     // 正确,因为这里会进行类型转换

在 s1 = s1 + 1; 中,s1 + 1 的结果被认为是 int 类型,因此需要进行强制转换才能赋值给 short 类型的变量 s1。而在 s1 += 1; 中,+= 操作符会对右边的表达式结果进行强制转换,因此不会报错。

所以,通过使用 += 操作符,可以避免一些由于类型提升导致的编译错误。

1.16 线程 进程 程序

线程:是进程的一个实体,是 cpu 调度和分派的基本单位,是比进程更小的
可以独立运行的基本单位。
进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操作
系统进行资源分配和调度的一个独立单位。
特点:线程的划分尺度小于进程,这使多线程程序拥有高并发性,进程在运行
时各自内存单元相互独立,线程之间 内存共享,这使多线程 编程可以拥有更好
的性能和用户体验。

程序 :含有指令和数据的文件
a.一个程序可以包含多个进程,而一个进程可以包含多个线程。程序是静态的代码和数据,进程是程序在执行时的一个实例,而线程是进程内的一个独立执行流。
**b.**进程之间是相互独立的,一个进程的崩溃不会影响其他进程。而线程之间共享同一进程的资源,因此线程之间的操作需要同步。
**c.**多线程的主要优势在于提高程序的并发性和效率,可以同时执行多个任务,充分利用多核处理器的优势。但同时,多线程的编程也需要注意线程安全性和同步问题。

1.17 线程建立的方式 4种

(1)通过继承 Thread 类,重写 run 方法来定义线程执行的任务。

public class MyThread extends Thread {
    public void run() {
        // 线程执行的任务
    }
}

// 创建线程实例
Thread myThread = new MyThread();
// 启动线程
myThread.start();

(2)通过实现 Runnable 接口,实现 run 方法,可以避免 Java 单继承的限制,更加灵活。

public class MyRunnable implements Runnable {
    public void run() {
        // 线程执行的任务
    }
}

// 创建线程实例
Thread myThread = new Thread(new MyRunnable());
// 启动线程
myThread.start();

(3)通过实现 Callable 接口,实现 call 方法,可以获取线程执行的结果,并且可以抛出异常。

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class MyCallable implements Callable<String> {
    public String call() {
        // 线程执行的任务
        return "任务执行结果";
    }
}

// 创建线程实例
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread myThread = new Thread(futureTask);
// 启动线程
myThread.start();
// 获取线程执行结果
String result = futureTask.get();

(4)使用线程池可以更好地管理和重用线程,提高程序性能。Java 提供了 Executor 框架用于线程池的管理

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        // 创建固定大小的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        // 提交任务给线程池
        executorService.execute(new MyRunnable());
        executorService.submit(new MyCallable());

        // 关闭线程池
        executorService.shutdown();
    }
}

new一个接受一个Callable 参数的FutureTask对象 那么这个对象就变成Runnable对象 因为thread只能接受runnable对象参数所以要用FutureTask
1.18 Runable 和Callable区别
(1)Runnable 接口 run 方法无返回值;
Callable 接口 call 方法有返回值,支持泛型
(2)Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;
Callable 接口 call 方法允许抛出异常,可以获取异常信息
异常

1.18Java 中 IO 流分为几种?

在 Java 中,IO(Input/Output)流用于处理输入和输出操作。Java 中的 IO 流主要分为两大类:字节流和字符流,每一类又分为输入流和输出流。因此,总共有四种基本的 IO 流:

1.字节流(Byte Streams):

(1)输入字节流(Byte Input Streams):

InputStream 是所有输入字节流的抽象基类。
FileInputStream 从文件中读取字节。
ByteArrayInputStream 从字节数组中读取字节。
等等…
(2)输出字节流(Byte Output Streams):

OutputStream 是所有输出字节流的抽象基类。
FileOutputStream 向文件中写入字节。
ByteArrayOutputStream 将字节写入字节数组。
等等…
2.字符流(Character Streams):

(1)输入字符流(Character Input Streams):

Reader 是所有输入字符流的抽象基类。
FileReader 从文件中读取字符。
StringReader 从字符串中读取字符。
等等…
(2)输出字符流(Character Output Streams):

Writer 是所有输出字符流的抽象基类。
FileWriter 向文件中写入字符。
StringWriter 将字符写入字符串。
等等…
这些流提供了不同的方式来进行输入和输出,字节流主要用于处理二进制数据,而字符流主要用于处理字符数据。在实际编程中,选择使用字节流还是字符流取决于处理的数据类型。

二、JVM

2.11 JVM内存模型?

JVM内存模型:
线程独占:栈,本地方法栈,程序计数器
线程共享:堆,方法区
(1)线程独占

程序计数器(Program Counter Register):
每个线程都有自己的程序计数器,用于指示当前线程执行的字节码指令的地址。
在多线程环境中,各个线程的程序计数器是独立的,线程之间不共享。
Java 虚拟机栈(Java Virtual Machine Stacks):
每个线程在执行 Java 方法时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
线程独占,即每个线程都有自己的栈,互不影响。
本地方法栈(Native Method Stack):
与虚拟机栈类似,但是为执行本地(Native)方法服务。
(2)线程共享:
堆(Heap):
存储对象实例,包括应用程序创建的对象以及Java虚拟机自己管理的对象(如数组和垃圾回收信息)。
所有线程共享堆。
方法区(Method Area)
存储类的结构信息,包括类的元数据、静态变量、常量、静态代码块等。
注意事项:
0.栈的空间大小远远小于堆的
1.堆和方法区是线程共享的,而程序计数器、虚拟机栈和本地方法栈是线程私有的。
2.线程独占的内存区域随着线程的创建和销毁而创建和销毁,而线程共享的内存区域在JVM启3.动时创建,在JVM关闭时销毁。
4.栈帧是线程私有的,每个线程在执行方法时都会创建一个栈帧。
5.堆中的对象实例可以被多个线程访问,因此在多线程环境下需要注意对共享对象的同步

2.12类加载与卸载

1.类加载的过程:
加载(Loading):
类加载器通过类的全限定名(Fully Qualified Name)来定位并读取类的字节码文件。
字节码文件可以来自本地文件系统、网络、ZIP文件等。
链接(Linking)
链接阶段包括三个步骤:
验证(Verification)、准备(Preparation)、解析(Resolution)。

验证: 确保字节码文件符合Java虚拟机规范,不会导致安全问题。
准备: 为类的静态变量分配内存,并设置默认初始值。
解析: 将类、接口、字段和方法的符号引用解析为直接引用。
初始化(Initialization):
执行类的初始化代码,包括静态变量的赋值和静态初始化块的执行。
初始化是类加载的最后一步,它是线程安全的,并且只会执行一次。
2.触发条件
创建类的实例时:

创建类的实例并不会触发类的卸载。类的实例创建时主要是涉及到类的初始化。
访问类的静态方法或静态变量的时候:
访问静态方法或静态变量会触发类的初始化,但并不会导致类的卸载。
使用 Class.forName 反射类的时候:
使用 Class.forName 加载类会触发类的初始化,但同样不会导致类的卸载。
某个子类初始化的时候:
子类初始化也可能导致父类的初始化,但不会导致类的卸载
类卸载触发条件:
当类的所有实例都已经被回收,或者类的 ClassLoader 已经被回收,或者类的 finalize() 方法已经被调用且没有抛出异常或者被捕获时,可能触发类的卸载。这些条件属于可能导致类卸载的触发条件。**
3、加载机制-双亲委派模式
双亲委派模式,即加载器加载类时先把请求委托给自己的父类加载器执行,直到顶层的启动类加载器.父类加载器能够完成加载则成功返回,不能则子类加载器才自己尝试加载.
优点:

		1.避免类的重复加载
		2.避免Java的核心API被篡改

4、分代回收
分代回收基于两个事实:大部分对象很快就不使用了,还有一部分不会立即无用,但也不会持续很长时间.
年轻代->标记-复制
老年代->标记-清除
5.3、回收算法
a、G1算法
1.9后默认的垃圾回收算法,特点保持高回收率的同时减少停顿.采用每次只清理一部分,而不是清理全部的增量式清理,以保证停顿时间不会过长
其取消了年轻代与老年代的物理划分,但仍属于分代收集器,算法将堆分为若干个逻辑区域(region),一部分用作年轻代,一部分用作老年代,还有用来存储巨型对象的分区.
同CMS相同,会遍历所有对象,标记引用情况,清除对象后会对区域进行复制移动,以整合碎片空间.
年轻代回收:
并行复制采用复制算法,并行收集,会StopTheWorld.
老年代回收:
会对年轻代一并回收
初始标记完成堆root对象的标记,会StopTheWorld.
并发标记 GC线程和应用线程并发执行.
最终标记完成三色标记周期,会StopTheWorld.
复制/清楚会优先对可回收空间加大的区域进行回收
b、ZGC算法
前面提供的高效垃圾回收算法,针对大堆内存设计,可以处理TB级别的堆,可以做到10ms以下的回收停顿时间

		着色指针
		读屏障
		并发处理
		基于region
		内存压缩(整理)

roots标记:标记root对象,会StopTheWorld.
并发标记:利用读屏障与应用线程一起运行标记,可能会发生StopTheWorld.
清除会清理标记为不可用的对象.
roots重定位:是对存活的对象进行移动,以腾出大块内存空间,减少碎片产生.重定位最开始会StopTheWorld,却决于重定位集与对象总活动集的比例.
并发重定位与并发标记类似.

三、Spring

3.11.Spring的IOC和AOP机制

Spring框架是一个开源的Java框架,提供了全面的基础设施,用于构建企业级Java应用程序。其中,IOC(Inversion of Control)和AOP(Aspect-Oriented Programming)是Spring框架的两个核心特性。
(1)IOC(控制反转)
**IOC是一种设计模式,它将控制权从应用程序代码中反转,由框架来控制对象的创建和管理。**在传统的编程模型中,对象的创建和依赖关系是在应用程序代码中硬编码的,而在IOC容器中,这些控制权被反转了。
在Spring中,IOC的实现主要依赖于容器(ApplicationContext)。容器负责创建、配置和管理对象的生命周期。通过IOC,开发者不再需要手动创建对象,而是通过配置文件或注解来描述对象之间的依赖关系,容器负责实例化和管理这些对象。
IOC的优点包括:
松耦合: 由于对象的创建和依赖关系由容器管理,因此对象之间的耦合度降低,代码更容易维护和测试。
可维护性: 通过IOC容器,可以集中管理应用程序的配置信息,使得配置更加灵活,便于维护和修改。
可测试性: IOC容器使得对象的创建和依赖关系更容易模拟和测试,提高了代码的可测试性。
示例(Java中的Spring IOC示例):


// 使用IOC容器实现依赖注入
public class MyService {
    private MyRepository repository;

    // 通过构造函数注入依赖
    public MyService(MyRepository repository) {
        this.repository = repository;
    }

    // 业务方法
    public void doSomething() {
        // 使用注入的repository对象进行业务操作
        repository.saveData();
    }
}

// IOC容器配置
@Configuration
public class AppConfig {
    @Bean
    public MyRepository myRepository() {
        return new MyRepositoryImpl();
    }

    @Bean
    public MyService myService(MyRepository repository) {
        return new MyService(repository);
    }
}

(2)AOP(面向切面编程)
AOP是一种编程范式,它允许开发者通过在应用程序中插入横切关注点(cross-cutting concerns),如日志、事务管理等,来提高代码的模块化和可维护性。
在Spring中,AOP通 代理实现。通过AOP,可以将横切关注点从应用程序主体中分离出来,使得关注点的修改不会影响主体逻辑。AOP主要通过以下几个概念实现:
切面(Aspect): 切面定义了横切关注点以及在何处执行这些关注点。在Spring中,切面通常是一个包含通知(advice)和切点(pointcut)的类。
通知(Advice): 通知是切面中定义的具体行为,包括在切点之前、之后、或代替切点执行的操作。通知有多种类型,如前置通知、后置通知、环绕通知等。
切点(Pointcut): 切点定义了在何处执行通知。它是一个表达式,匹配到目标对象中的连接点(Join Point)。
通过AOP,可以将横切关注点模块化,提高代码的重用性和可维护性。
示例(Java中的Spring AOP示例):
AOP详细描述

// 定义切面
@Aspect
public class LoggingAspect {

    // 定义切点
    @Pointcut("execution(* com.example.service.*.*(..))")
    private void serviceMethods() {}

    // 定义前置通知
    @Before("serviceMethods()")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Before executing: " + joinPoint.getSignature().getName());
    }
}

综合来说,IOC和AOP是Spring框架的两个重要特性,IOC通过控制对象的创建和依赖关系来降低耦合度,而AOP通过在程序中插入横切关注点来提高代码的模块化和可维护性。这两个特性共同使得Spring成为一个灵活、可扩展、易维护的框架。
依赖注入
理解依赖注入时,可以将其看作一种通过外部方式向对象提供所需的依赖关系的机制。这是一种反转控制(Inversion of Control,IoC)的实现方式,其中对象不再负责自己依赖的创建和管理,而是由外部容器(通常是IoC容器)负责。

让我们通过一个简单的例子来说明依赖注入的概念。考虑以下两个类:

java
Copy code
// Service类依赖于Repository类
public class MyService {
private MyRepository repository;

// 构造函数注入依赖
public MyService(MyRepository repository) {
    this.repository = repository;
}

public void doSomething() {
    // 使用注入的repository对象进行业务操作
    repository.saveData();
}

}

public class MyRepository {
public void saveData() {
// 保存数据的具体实现
}
}
在这个例子中,MyService 类依赖于 MyRepository 类。依赖注入的方式之一是通过构造函数注入,如上述例子中所示。在 MyService 类的构造函数中,它接收一个 MyRepository 类型的参数,这就是依赖注入的过程。通过这种方式,MyService 不再负责创建 MyRepository 对象,而是将该依赖关系委托给外部。依赖的意思就是使用是意思 ,myservice类要用myrepository所以要用ioc容器注入myrepository对象

3.12 Spring中Autowired和Resource关键字的区别?

@Autowired 和 @Resource 是 Spring 框架中用于依赖注入的注解,它们有一些区别,主要体现在以下几个方面:
注入方式:
@Autowired 是Spring提供的注解,它通过类型匹配进行自动装配。当多个候选bean类型与要注入的字段或方法参数的类型匹配时,Spring会选择最合适的bean进行注入。
@Resource 是Java规范的注解,由Java EE规范定义。它默认按照名称进行自动装配,但如果指定了 name 属性,则按照指定的名称进行装配。
支持的注入点:
@Autowired 可以用于构造函数、字段、方法、以及方法参数上。
@Resource 主要用于字段、方法,以及方法参数上,不推荐在构造函数上使用。
使用的容器:
@Autowired 是Spring提供的注解,因此主要用于Spring容器中。
@Resource 是Java EE规范的一部分,理论上可以在任何Java EE容器中使用,但在实际应用中通常与Spring一起使用。
可选属性:
@Autowired 的 required 属性默认为 true,表示注入的依赖必须存在。如果没有找到匹配的bean,且 required 设置为 true,则会抛出异常。如果设置为 false,则会忽略找不到匹配bean的情况。
@Resource 的 required 属性默认为 true,表示要注入的依赖必须存在。如果没有找到匹配的bean,且 required 设置为 true,则会抛出异常。不同的是,@Resource 还有一个 name 属性,可以用于指定要注入的bean的名称,如果没有指定,则默认按照字段名或方法名进行匹配。

3.13 依赖注入的方式有几种,各是什么?

(1)构造器注入(Constructor Injection):

通过构造函数将依赖项注入到类中。通过这种方式,可以在创建对象的时候就确保所有的依赖项都被满足。
在类的构造函数上使用 @Autowired 注解或者通过 XML 配置文件中的 元素实现构造器注入。

public class MyClass {
    private MyDependency myDependency;

    @Autowired
    public MyClass(MyDependency myDependency) {
        this.myDependency = myDependency;
    }
}

(2)Setter方法注入(Setter Injection):

通过Setter方法为依赖项提供注入点。通过这种方式,可以在对象创建后通过Setter方法随时注入依赖项。
在Setter方法上使用 @Autowired 注解或者通过 XML 配置文件中的 元素实现Setter方法注入。

public class MyClass {
    private MyDependency myDependency;

    @Autowired
    public void setMyDependency(MyDependency myDependency) {
        this.myDependency = myDependency;
    }
}

(3)字段注入(Field Injection)

通过直接注入字段来满足依赖关系。虽然方便,但一些人认为这样的注入方式破坏了封装性。
在字段上使用 @Autowired 注解或者通过 XML 配置文件中的 元素实现字段注入。

通过直接注入字段来满足依赖关系。虽然方便,但一些人认为这样的注入方式破坏了封装性。
在字段上使用 @Autowired 注解或者通过 XML 配置文件中的 <property> 元素实现字段注入。

在Spring框架中,依赖注入有三种主要的方式:

构造器注入(Constructor Injection):

通过构造函数将依赖项注入到类中。通过这种方式,可以在创建对象的时候就确保所有的依赖项都被满足。
在类的构造函数上使用 @Autowired 注解或者通过 XML 配置文件中的 元素实现构造器注入。
java
Copy code
public class MyClass {
private MyDependency myDependency;

@Autowired
public MyClass(MyDependency myDependency) {
    this.myDependency = myDependency;
}

}
Setter方法注入(Setter Injection):

通过Setter方法为依赖项提供注入点。通过这种方式,可以在对象创建后通过Setter方法随时注入依赖项。
在Setter方法上使用 @Autowired 注解或者通过 XML 配置文件中的 元素实现Setter方法注入。
java
Copy code
public class MyClass {
private MyDependency myDependency;

@Autowired
public void setMyDependency(MyDependency myDependency) {
    this.myDependency = myDependency;
}

}
字段注入(Field Injection):

通过直接注入字段来满足依赖关系。虽然方便,但一些人认为这样的注入方式破坏了封装性。
在字段上使用 @Autowired 注解或者通过 XML 配置文件中的 元素实现字段注入。
java
Copy code
public class MyClass {
@Autowired
private MyDependency myDependency;
}
选择注入方式通常取决于个人或团队的偏好,以及在特定情况下的需求。构造器注入通常被认为是最佳实践,因为它确保对象创建时的完全性,并使得对象在创建后就处于可用状态。 Setter注入和字段注入则提供了更灵活的方式,但可能会导致对象在构造完成后处于不完整状态。

3.14 简述Spring MVC流程

在这里插入图片描述

Spring MVC是一个基于MVC(Model-View-Controller)模式的Web框架,用于开发Web应用程序。下面是Spring MVC的基本流程:

1.请求到达 DispatcherServlet:
当客户端发起一个HTTP请求时,请求首先到达Spring MVC的DispatcherServlet,它是整个Spring MVC流程的中心。
2.Handler Mapping:
DispatcherServlet 通过HandlerMapping(处理器映射器)来确定请求的处理器(Controller)。HandlerMapping会根据请求的URL映射到对应的Controller。
3.Controller处理请求:
一旦找到匹配的Controller,DispatcherServlet会将请求转发给相应的Controller进行处理。Controller是一个包含业务逻辑的组件,它处理请求并返回相应的模型(Model)和视图(View)。
4.ModelAndView返回:

Controller处理请求后,通常会创建一个ModelAndView对象,其中包含了模型数据和视图的信息。模型数据是在Controller中准备的,而视图是由ViewResolver(视图解析器)解析得到的。
5.ViewResolver解析视图:
DispatcherServlet 将 ModelAndView 传递给 ViewResolver,它会根据视图的逻辑名称(通常是JSP文件名或其他视图技术的标识)解析成具体的视图对象。
6.渲染视图:
一旦确定了视图对象,DispatcherServlet 将调用视图对象的渲染方法来生成最终的HTML响应。
7.响应返回客户端:
DispatcherServlet 最终将响应返回给客户端,完成整个请求-响应周期。

这是Spring MVC的基本流程,其中 DispatcherServlet 负责协调整个过程,HandlerMapping 负责映射请求到具体的Controller,ViewResolver 负责解析视图。在Controller中,开发者可以处理请求,准备模型数据,然后通过 ModelAndView 返回相应的视图。整个流程实现了松耦合和可扩展性,使得开发者能够更容易地组织和维护Web应用程序。

3.15spring生命周期

1.实例化(Instantiation):
当Spring容器启动时,它会实例化配置文件中定义的所有Bean。这通常涉及到调用Bean的构造方法来创建Bean的实例。
2.属性赋值(Population of properties):
在实例化后,Spring容器会将配置文件中定义的属性值(通过构造函数、Setter方法等方式定义的属性)注入到Bean实例中。
3.初始化(Initialization):
一旦属性赋值完成,Spring容器会调用Bean的初始化方法。这个初始化方法可以是自定义的,开发者可以通过配置文件中的 init-method 属性或使用 @PostConstruct 注解来指定。
4.使用(In Use):
Bean已经被完全初始化,并且可以被应用程序使用了。在这个阶段,Bean可以响应应用程序的请求。
5.销毁(Destruction):
当应用程序关闭或者Spring容器销毁时,Spring容器会调用Bean的销毁方法。这个销毁方法可以是自定义的,开发者可以通过配置文件中的 destroy-method 属性或使用 @PreDestroy 注解来指定。

3.16 controller层

Controller层是MVC(Model-View-Controller)架构中的一部分,负责接收用户的输入、处理用户请求,并调用相应的业务逻辑(Service层)进行处理。Controller层主要负责协调和控制应用程序的流程,将用户的请求映射到相应的业务逻辑,并将处理结果返回给用户。

SpringMVC常用的注解有哪些?

1.@RequestMapping: 是Spring框架中用于映射HTTP请求路径的注解
2.@RequestBody:注解实现接收http请求的json数据,将json转换为java对象。
3.@ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户

4.Mysql

4.11. 复习所有常见关键字

以下是一个包含MySQL的常见关键字的简单SQL查询语句示例:
SELECT 
    user_id,
    COUNT(order_id) AS order_count
FROM 
    orders
WHERE 
    order_date >= '2023-01-01'
GROUP BY 
    user_id
HAVING 
    order_count > 2
ORDER BY 
    order_count DESC
LIMIT 
    10;
在这个例子中,包含了以下 MySQL 的关键字和子句:

SELECT: 选择要查询的列。
FROM: 指定要查询的表。
WHERE: 过滤条件,仅选择满足条件的行。
GROUP BY: 按照指定的列对结果进行分组。
HAVING: 对分组后的结果进行筛选。
ORDER BY: 对结果进行排序,可以指定升序(ASC)或降序(DESC)。
LIMIT: 限制结果集的行数。
这个例子查询了在某个日期之后有超过两个订单的用户,并按订单数量降序排序,
最后只返回前10行结果。

4.12常见函数

1.聚合函数
COUNT(): 计算行数。
SELECT COUNT(*) FROM table_name;
SUM(): 计算总和。
SELECT SUM(column_name) FROM table_name;
AVG(): 计算平均值。
SELECT AVG(column_name) FROM table_name;
MAX(): 返回最大值。
SELECT MAX(column_name) FROM table_name;
MIN(): 返回最小值。
SELECT MIN(column_name) FROM table_name;
2.字符串函数:
CONCAT(): 连接字符串。
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM employees;
SUBSTRING(): 提取子字符串。
SELECT SUBSTRING(column_name, 1, 3) FROM table_name;
UPPER() / LOWER(): 将字符串转换为大写/小写。
SELECT UPPER(column_name) FROM table_name;
3、日期和时间函数:
NOW(): 返回当前日期和时间。
SELECT NOW();
DATE_FORMAT(): 格式化日期。
SELECT DATE_FORMAT(date_column, '%Y-%m-%d') FROM table_name;
4.逻辑函数:
IF(): 条件判断。
SELECT column_name, IF(column_name > 0, 'Positive', 'Negative') AS result FROM table_name;
5.数学函数:
ROUND(): 四舍五入。
SELECT ROUND(column_name, 2) FROM table_name;
RAND(): 生成随机数。
SELECT RAND() FROM table_name;

4.13.什么是索引?

在数据库和计算机科学领域,索引(Index)是一种数据结构,用于快速查找和访问数据库表中的特定数据行。索引的存在可以大大提高数据库的查询性能,尤其是对于大型数据集。
常见的索引类型包括:

1.聚集索引(Clustered Index): 对数据表的整体进行排序,实际上改变了表的物理顺序。一个表只能有一个聚集索引。

2.非聚集索引(Non-Clustered Index): 对数据表的副本进行排序,实际上创建了一个独立的数据结构来加速查询。一个表可以有多个非聚集索引。

3.唯一索引(Unique Index): 确保索引列中的所有数据都是唯一的,用于确保数据完整性。

4.复合索引(Composite Index): 基于表的多个列创建的索引,可以在多个列上提高查询性能。

4.14 什么是内联接、左外联接、右外联接?

内容
1.内联接(Inner Join):匹配2张表中相关联的记录。
2.左外联接(Left Outer Join):除了匹配2张表中相关联的记录外,还会匹配左表中剩余的记录,右表中未匹配到的字段用NULL表示。
3.右外联接(Right Outer Join):除了匹配2张表中相关联的记录外,还会匹配右表中剩余的记录,左表中未匹配到的字段用NULL表示。在判定左表和右表时,要根据表名出现在Outer Join的左右位置关系。

4.15 并发事务带来哪些问题?

的性能和吞吐量,但也引入了一些问题,主要包括以下几个方面:

丢失更新(Lost Updates):

当多个事务同时访问和更新相同的数据时,可能导致其中一个事务的更新被另一个事务覆盖,从而导致部分更新的数据丢失。
脏读(Dirty Reads):

当一个事务读取了另一个事务尚未提交的未经确认的数据时,就发生了脏读。如果另一个事务后来被回滚,读取的数据实际上是无效的,可能导致不一致的结果。
不可重复读(Non-Repeatable Reads):

当一个事务在读取某个数据项之后,另一个事务修改了该数据项并提交了事务,导致第一个事务再次读取同一数据项时,得到的结果与之前不一致。
幻读(Phantom Reads):

幻读指的是在一个事务中执行范围查询时,另一个事务插入、更新或删除了符合该查询条件的数据,导致第一个事务在同一范围内重新查询时发现了新增或减少的数据。
并发控制开销:

五.redis

5.11 简述redis的五种数据结构 并详细举例

1.字符串(String):

示例:
bash
Copy code
SET key1 "Hello"
GET key1  # 返回 "Hello"

2.哈希表(Hash):

示例:
user:1只是hash对象的检索  也可以改成其他的随便字符
HMSET user:1 username alice age 25 email alice@example.com
HGETALL user:1
#返回 username: "alice", age: "25", email: "alice@example.com"

3.列表(List):

示例:
bash
Copy code
LPUSH mylist "world"
LPUSH mylist "hello"
LRANGE mylist 0 -1
#返回 ["hello", "world"]

4.集合(Set):

示例:
bash
Copy code
SADD myset "apple"
SADD myset "banana"
SMEMBERS myset
# 返回 ["apple", "banana"]

5.有序集合(Sorted Set):

示例:
bash
Copy code
ZADD myzset 1 "one"
ZADD myzset 2 "two"
ZRANGE myzset 0 -1 WITHSCORES
# 返回 [("one", 1), ("two", 2)]

这些数据结构提供了不同的方式来组织和存储数据,适用于不同的使用场景。例如,字符串用于存储简单的键值对,哈希表用于存储对象属性,列表用于实现队列或堆栈,集合用于存储唯一的元素,有序集合则在集合的基础上增加了排序功能。

5.12.

5.13.

六.多线程与高并发

6.11.Java中实现多线程有几种方法

继承Thread类;
实现Runnable接口;
实现Callable接口通过FutureTask包装器来创建Thread线程;
使用ExecutorService、Callable、Future实现有返回结果的多线程(也就是使用了****ExecutorService来管理前面的三种方式)
**
1. 继承Thread类:

public class MyThread extends Thread {
    @Override
    public void run() {
        // 线程执行的任务
        System.out.println("Thread is running...");
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

2. 实现Runnable接口:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 线程执行的任务
        System.out.println("Runnable is running...");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}

3. 实现Callable接口通过FutureTask包装器:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        // 线程执行的任务,并返回结果
        return "Callable is running...";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
        Thread thread = new Thread(futureTask);
        thread.start();
        String result = futureTask.get(); // 获取线程执行的结果
        System.out.println(result);
    }
}

4. 使用ExecutorService、Callable、Future:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        
        Callable<String> callable = () -> {
            // 线程执行的任务,并返回结果
            return "Callable is running...";
        };

        Future<String> future = executorService.submit(callable);

        executorService.shutdown();

        String result = future.get(); // 获取线程执行的结果
        System.out.println(result);
    }
}

这四种方式分别展示了使用继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程,以及使用ExecutorService、Callable、Future实现有返回结果的多线程。选择其中的一种方式取决于你的具体需求和设计偏好。通常来说,推荐使用实现Runnable接口或实现Callable接口的方式,因为它们更灵活,允许多继承和更好的代码组织。 ExecutorService提供了更高级别的线程管理和控制。

在使用Thread类的构造方法时,你可以直接传入实现Runnable接口的对象,因为Thread类的构造方法接受Runnable类型的参数。这是因为Thread类实际上实现了Runnable接口,所以你可以将实现了Runnable接口的对象传递给Thread的构造方法。
在上面的例子中,FutureTask类也实现了Runnable接口,因此你可以将FutureTask对象直接传递给Thread的构造方法。这样做的好处是可以在需要时更容易地切换任务的执行方式,而不需要修改线程创建的部分如果你有一个实现了Callable接口的对象(比如MyCallable),你可以选择直接将MyCallable对象传递给FutureTask的构造方法,然后将FutureTask对象传递给Thread的构造方法,。
**

6.12notify()和 notifyAll()有什么区别?

两概念:Java中的 等待池、锁池。
等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中。等待池中的线程不会去竞争该对象的锁。
锁池:只有获取了对象的锁,线程才能执行对象的 synchronized 代码,对象的锁每次只有一个线程可以获得,其他线程只能在锁池中等待。
notify、notifyAll 的区别:
如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争。
优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

6.13. volatile 是什么?可以保证有序性吗?

volatile 是Java中的一个关键字,用于修饰实例变量。使用 volatile 关键字修饰的变量具有以下特性:
可见性(Visibility): 当一个线程修改 volatile 变量的值时,这个变化对于其他线程是可见的。这是因为每次读取 volatile 变量时,都会从主内存中重新加载,而不是使用线程的本地缓存。
禁止指令重排序(Prevents Instruction Reordering): volatile 关键字禁止编译器和处理器对其进行一些优化,可以确保变量的读写操作按照程序代码的顺序执行。
尽管 volatile 提供了可见性和禁止指令重排序的特性,但它并不能保证有序性。
当多个线程访问共享变量时,由于线程调度的不确定性,可能会导致线程执行顺序的不确定性。使用 volatile 能够确保可见性,但并不能保证一系列操作的原子性。如果需要保证一系列操作的原子性和有序性,可以考虑使用锁或者其他并发控制机制。

6.14 为什么wait, notify 和 notifyAll这些方法不在thread类里面?

JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。
理解:
1.多线程概述
Java是一个支持多线程的开发语言,多线程并发执行任务可以充分利用CPU资源,提高多任务并发执行效率(注意区分:多线程并不会加快任务的执行速度,而是可以充分利用多核CPU让线程轮流进行工作,达到了一种“同时”工作的效果)。

2.并发时的产生问题
多线程在执行时,会遇到一些问题,问题的关键原因则是在共享资源的更新操作上容易产生冲突。

3.解决的方向
解决冲突的方式则是从共享资源的占用机制入手,保证共享资源同一时刻只能被一个线程占用,从而达到数据一致。

4.具体实现方式中的一种
在Java中提供了synchorinzed关键字,在该关键字修饰的代码内,称为同步代码块,线程执行该区域代码需要获取锁,在获取成功之后,其他线程需要等该线程执行完毕释放锁之后才能获取到。

5.wait、notify、notifyAll方法的作用
在同步代码块中,可以使用wait、notify来控制当前占用资源的线程进入阻塞队列与唤醒进入就绪队列
也就是说,上述两个方法实际上实现的时线程之间的通信机制,用来通知线程的阻塞与唤醒。

6.引出问题:为什么定义在Object中而不是Thread类
线程为了进入临界区(也就是同步块内),需要获得锁并等待锁可用,它们并不知道也不需要知道哪些线程持有锁,它们只需要知道当前资源是否被占用,是否可以获得锁,所以锁的持有状态应该由同步监视器来获取,而不是线程本身。
7.形象的比喻:一个女孩子同时被10个男孩子追,在同一个时间段内只能陪一个男孩子看电影,当其中一个男孩想要邀请女孩看电影时,应该由女孩子来通知男孩子,这个时间段可不可以赴约,而不应该是男孩子询问其他情敌抑或是由情敌之间相互通知谁该去赴约(那还不打起来了)。
例子中女孩相当于”共享资源“(每一个男孩机会均等),在Java中是一个对象,男孩子们相当于线程,多个线程之间的通信机制,由共享资源(就是前文所说的同步监视器)来实现,即将wait、notify、notifyAll方法定义在Object(女孩)中是最合理的
8.在以下代码的三只猴子抢香蕉中,要进入synchronized()代码块就要获取锁对象(为test2.class) 三个猴子线程来争这个对象的香蕉,

public class test2 {
    //public class test {
  int bananaCount=10;

    public static void main(String[] args) {
        test2 t=new test2();
        MonKey monKey1=t.new MonKey("猴子1");
        MonKey monKey2=t.new MonKey("猴子2");
        MonKey monKey3=t.new MonKey("猴子3");
        monKey1.start();
        monKey2.start();
        monKey3.start();
    }
    class MonKey extends Thread{
        //构造函数
        public MonKey(String name){
            super(name);
        }
        @Override
        public void run() {
            while(bananaCount>0){
                synchronized(test2.class){
                    if(bananaCount>0){
                        System.out.println(getName()+"抢到一个香蕉还剩"+--bananaCount);
                    }
                    else{
                        System.out.println("抢完了。。。");
                        break;
                    }
                }
                try {
                    // 模拟猴子抢香蕉的过程,延迟一秒
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }


        }
    }


}

一个线程获取到对象的锁后执行线程,如果对象引用wait的话,线程就会释放该对象的锁,进入等待池等待该对象锁,如果wait方法在线程上的话会等待的是线程的锁,就会无法获取对象锁,对象锁是共享的。由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象,在obeject类才能知道对哪个锁操作 等待哪个锁 唤醒哪个锁。每个对象都有锁,通过线程获得。在Java中,每个对象都有一个相关联的锁,也称为监视器锁。这个锁是通过关键字synchronized来获取的。当一个线程希望进入一个被synchronized关键字保护的代码块时,它需要先获得与该对象关联的锁

6.15 Java中interrupted 和 isInterrupted方法的区别?

interrupted() 和 isInterrupted() 是用于处理线程中断的两个方法,但它们之间有一些重要的区别:

1.interrupted() 方法:

interrupted() 方法是一个静态方法,它是Thread类的静态方法。
当 interrupted() 方法被调用时,它会测试当前线程是否被中断,并清除中断状态。如果线程被中断,则返回 true;否则返回 false。
除了返回中断状态外,调用 interrupted() 还会清除中断状态,将中断状态重新置为 false。
该方法是一个静态方法,因此调用形式为 Thread.interrupted()。

示例:
boolean interrupted = Thread.interrupted();

2.isInterrupted() 方法:

isInterrupted() 是一个实例方法,它是Thread类的实例方法。
当 isInterrupted() 被调用时,它会测试调用该方法的线程是否被中断,但不会清除中断状态。如果线程被中断,则返回 true;否则返回 false。
与 interrupted() 不同,isInterrupted() 不会改变中断状态。

6.16 Java中synchronized 和 ReentrantLock 有什么不同?

在Java中,synchronized 和 ReentrantLock 都是用于实现线程同步的机制,但它们有一些关键的区别。以下是它们之间的一些主要不同点:

1.使用方式:

synchronized 是Java的内置关键字,用于声明方法或代码块的同步。不需要手动创建锁对象。
ReentrantLock 是java.util.concurrent包中的类,需要显式创建对象,手动获取和释放锁。
2.可重入性:

synchronized 和 ReentrantLock 都是可重入的,允许线程多次获取同一把锁。
3.灵活性:

ReentrantLock 提供了更多灵活性,支持公平锁、非公平锁,并提供高级特性如超级获取锁、中断等待锁等。
synchronized 相对简单,不提供太多额外的控制选项。
4.条件等待:

ReentrantLock 提供了 Condition 接口,可以通过 ReentrantLock 对象创建多个条件对象,从而可以在不同的条件上等待或唤醒线程。这种机制在一些高级线程同步场景中很有用。
synchronized 也支持条件等待,但是相对于 ReentrantLock 的灵活性较差
一般来说,如果只是进行简单的线程同步,而不需要额外的高级特性,synchronized 是一个更简单和方便的选择。如果需要更多的控制和灵活性,或者需要使用高级特性,那么可以考虑使用 ReentrantLock。

6.17.有三个线程T1,T2,T3,如何保证顺序执行?

使用 join 方法:

让每个线程在它前面的线程上调用 join 方法。例如,T2 在 T1 上调用 join,T3 在 T2 上调用 join。这样会确保 T1 先执行完,然后 T2 执行,最后 T3 执行。
Thread T1 = new Thread(() -> {
// 任务1
});

public class ThreadExecutionOrder {
    public static void main(String[] args) {
        // 线程T1,执行任务1
        Thread T1 = new Thread(() -> {
            System.out.println("任务1");
        });

        // 线程T2,执行任务2,在T1执行完毕后再执行
        Thread T2 = new Thread(() -> {
            try {
                T1.join(); // T2 在T1之后执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("任务2");
        });

        // 线程T3,执行任务3,在T2执行完毕后再执行
        Thread T3 = new Thread(() -> {
            try {
                T2.join(); // T3 在T2之后执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("任务3");
        });

        // 启动三个线程
        T1.start();
        T2.start();
        T3.start();
    }
}

对以上的lambda表达式解释: thead参数是runnable类型 实际上(不用lambda时)要让一个类实现runnable接口 然后重写run方法 再将该对象传到thread里面 用lambda表达式可以不用知道如何实现runnable如何重写实体类 直接写在lambda力()->{} ,():是传参数的,这里run是没参数的{重写run}

6.18 什么是线程安全?

线程安全(Thread Safety) 指的是在多线程环境下,一个对象或者代码块能够被多个线程安全地访问,而不会出现数据不一致或者不正确的情况。

6.19说一说自己对于 synchronized 关键字的了解

synchronized 是 Java 中的一个关键字,用于实现线程同步,确保多个线程对共享资源的访问是安全的。synchronized 关键字可以用于方法或代码块,以确保在同一时刻只有一个线程可以访问被保护的代码。

6.20.简述一下你对线程池的理解?

线程池(Thread Pool)是一种管理和复用线程的机制,用于提高多线程应用程序的性能和资源利用率。线程池通过维护一定数量的线程,并在需要时重用它们,避免了线程的频繁创建和销毁,减轻了系统的负担。
合理利用线程池能够带来三个好处。

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

6.21.实现Runnable接口和Callable接口的区别?

1.返回值Runnable 无
2.重写的方法不一样
3.异常 Runnable :无抛出检查异常

Runnable 接口和 Callable 接口都是用于创建多线程的接口,但它们之间有一些重要的区别:

1.Runnable 接口:
1.1.返回值: Runnable 接口的 run 方法没有返回值,因此任务无法直接返回结果。

1.2.异常抛出: run 方法不能抛出受检查异常,只能捕获并在方法内部处理。

1.3适用场景: 通常用于需要执行的任务不需要返回结果,或者通过共享变量来传递结果。

1.4.使用方式: 通过实现 Runnable 接口并重写 run 方法,将任务逻辑放在 run 方法中。

public class MyRunnable implements Runnable {
    public void run() {
        // 任务逻辑
    }
}

2.Callable 接口:
2.1.返回值: Callable 接口的 call 方法可以返回一个结果,这个结果可以被线程调用者获取。

2.2.异常抛出: call 方法可以抛出受检查异常,需要在方法签名中声明。

2.3适用场景: 适用于需要执行的任务返回结果,或者需要捕获并处理受检查异常的情况。

2.4使用方式: 通过实现 Callable 接口并重写 call 方法,将任务逻辑放在 call 方法中。

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
    public String call() throws Exception {
        // 任务逻辑,可以返回结果
        return "Task completed";
    }
}

使用方式:
使用 Runnable:

Runnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();

使用 Callable:
Callable myCallable = new MyCallable();
// 使用 ExecutorService 提交 Callable 任务
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future future = executorService.submit(myCallable);
// 获取任务执行结果
try {
String result = future.get();
System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}

总的来说,Runnable 适用于不需要返回结果的简单任务,而 Callable 更适用于需要返回结果或者处理受检查异常的任务。在实际应用中,根据具体需求选择合适的接口。

6.22

6.23.创建线程池的方式?

在 Java 中,你可以使用 ExecutorService 接口及其实现类来创建线程池。以下是几种常见的创建线程池的方式:

1. FixedThreadPool(固定大小线程池):

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
ExecutorService executorService = Executors.newFixedThreadPool(3);

这将创建一个固定大小为 3 的线程池。适用于需要控制并发线程数量的情况。

2. CachedThreadPool(可变大小线程池):

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
ExecutorService executorService = Executors.newCachedThreadPool();

这将创建一个根据需要创建新线程的线程池,没有固定大小。适用于执行很多短期异步任务的场景。

3. SingleThreadExecutor(单线程线程池):

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
ExecutorService executorService = Executors.newSingleThreadExecutor();

这将创建一个单线程的线程池,适用于需要按顺序执行任务的场景

4. ScheduledThreadPool(定时任务线程池):

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);

这将创建一个固定大小为 2 的线程池,适用于需要定时执行任务的场景。

5. ThreadPoolExecutor(自定义线程池):

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

// 自定义线程池参数
int corePoolSize = 5;
int maximumPoolSize = 10;
long keepAliveTime = 5000;
TimeUnit unit = TimeUnit.MILLISECONDS;

ExecutorService executorService = new ThreadPoolExecutor(
    corePoolSize,
    maximumPoolSize,
    keepAliveTime,
    unit,
    // 任务队列,可以选择使用 LinkedBlockingQueue 或其他队列实现
    new LinkedBlockingQueue<Runnable>()
);·

这是一种更为灵活的方式,可以自定义线程池的核心线程数、最大线程数、线程空闲时间等参数。

七.MyBatis篇.

7.11 没有用#{}的形式是怎样的为什么会sql注入?

在MyBatis中,#{} 是一种占位符语法,用于在SQL语句中安全地引入参数。使用 #{} 可以确保参数值在构建SQL语句时会被正确地转义和处理,防止SQL注入攻击。

如果没有使用 #{} 的形式,而是直接将参数值拼接到SQL语句中,就会存在SQL注入的风险。SQL注入是一种攻击手段,攻击者试图在输入数据中插入SQL代码,从而破坏原有的SQL语句结构,甚至获取敏感信息或执行恶意操作。

以下是一个简单的例子,展示了没有使用 #{} 导致的SQL注入风险:

public User getUserByUsernameAndPassword(String username, String password) {
    String sql = "SELECT * FROM user WHERE username='" + username + "' AND password='" + password + "'";
    // 执行 SQL 查询
    // ...
}

在上述例子中,username 和 password 直接拼接到 SQL 语句中,如果用户输入的内容包含恶意的 SQL 代码,就可能导致SQL注入。例如,如果用户输入的 username 是 “admin’ OR ‘1’=‘1’”,整个SQL语句就变成了

SELECT * FROM user WHERE username='admin' OR '1'='1' AND password='...'

这样的SQL语句会返回所有用户的信息,绕过了原始的验证逻辑。

为了防止SQL注入,应该始终使用 #{} 占位符,让MyBatis来处理参数的转义和安全性。例如:

public User getUserByUsernameAndPassword(@Param("username") String username, @Param("password") String password) {
    String sql = "SELECT * FROM user WHERE username=#{username} AND password=#{password}";
    // 执行 SQL 查询
    // ...
}

在上述例子中,#{username} 和 #{password} 是MyBatis的占位符语法,MyBatis会负责正确地处理参数,防止SQL注入。

7.12.什么是MyBatis ?

MyBatis)是一个开源的持久层框架,它主要用于将数据库操作与 Java 编程语言的对象模型之间进行映射,提供了一种优雅且灵活的数据库访问方式。MyBatis的设计目标是通过简化数据库操作,使开发人员能够更专注于 SQL 查询和结果映射,而不必过多关注数据库连接、事务管理等底层细节。
以下是MyBatis的一些主要特点和功能:

1.简单的XML或注解配置: MyBatis通过XML文件或注解配置SQL语句和映射关系,使得开发人员能够灵活地管理和维护SQL语句。

2.灵活的映射: MyBatis提供了灵活的结果映射,可以将查询结果映射为Java对象,支持复杂的关联关系。

3.动态SQL: MyBatis支持使用动态SQL构建查询语句,使得在运行时能够根据条件动态生成SQL语句,提高了查询的灵活性。

4.自动映射: MyBatis可以自动将查询结果映射到Java对象,无需手动编写映射代码。

5.缓存机制: MyBatis提供了一级缓存和二级缓存,帮助提高系统性能,减少数据库访问次数。

6.事务支持: MyBatis支持事务管理,可以通过编程式或声明式的方式进行事务控制。

7.插件机制: MyBatis具有插件机制,可以通过插件扩展框架的功能,例如自定义拦截器等。

8.支持存储过程: MyBatis支持调用和处理存储过程。

MyBatis被广泛应用于Java项目中,特别是与关系型数据库的交互。它的设计理念和功能使得数据库访问变得简单、灵活,并且具备良好的可维护性。

7.13.

八、Springboot

8.11什么是springboot?

Spring Boot它是基于Spring框架的,旨在简化和加速Spring应用程序的开发和部署。Spring Boot提供了一种约定大于配置的方式,通过默认设置和自动化配置,使得开发人员可以更专注于业务逻辑的实现,而不必过多关注配置和框架集成。
一、独立运行
Spring Boot而且内嵌了各种servlet容器,Tomcat、Jetty等,现在不再需要打成war包部署到容器中,Spring Boot只要打成一个可执行的jar包就能独立运行,所有的依赖包都在一个jar包内。
二、简化配置
spring-boot-starter-web启动器自动依赖其他组件,简少了maven的配置。
三、自动配置
Spring Boot能根据当前类路径下的类、jar包来自动配置bean,如添加一个spring-boot-starter-web启动器就能拥有web的功能,无需其他配置。
四、无代码生成和XML配置
Spring Boot配置过程中无代码生成,也无需XML配置文件就能完成所有配置工作,这一切都是借助于条件注解完成的,这也是Spring4.x的核心功能之一。
五、应用监控
Spring Boot提供一系列端点可以监控服务及应用,做健康检测。

8.12 哪springboot中哪里用打了反射

依赖注入(Dependency Injection): Spring Boot 使用反射来实现依赖注入。在你的应用程序中,你可以使用 @Autowired 注解来注入依赖关系。Spring 通过反射来检查和设置注入的属性或构造函数参数。
组件扫描: Spring Boot 通过扫描包路径来发现和注册组件(例如,@Controller、@Service、@Repository等)。这涉及到使用反射来检查类的注解和创建相应的对象。

AOP面向切面编程: Spring Boot 使用 AOP 实现一些横切关注点,例如事务管理、日志记录等。AOP 在运行时通过代理生成机制,使用了反射来创建代理对象,并在方法执行前后织入横切逻辑。
动态代理: Spring Boot 中的一些特性,例如缓存、事务管理等,使用了动态代理。动态代理是通过反射在运行时创建代理对象的一种机制。
数据绑定: Spring Boot 中使用了数据绑定来将外部配置文件中的属性值绑定到 Java 对象上。这个过程中涉及到通过反射设置对象的字段。

BeanPostProcessor: Spring 框架中有一些接口,如 BeanPostProcessor,允许在 bean 初始化前后进行处理。这涉及到使用反射来检查和修改 bean 的属性。

8.13

  • 19
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值