JAVA面试微总结

# 一、Spring面试题总结

## 1、spring是什么

- 一个轻量级的IOC和AOP容器框架;
- 常见的配置方式
  - 基于XML的配置
  - 基于注解的配置
  - 基于Java的配置
- Spring Core:核心类库,提供IOC服务
- Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI,定时任务)
- Spring AOP:AOP服务
- Spring DAO:对JDBC的抽象,简化了数据访问异常的处理
- Spring Web/Spring ORM/Spring MVC

## 2、Spring的优点

- spring的依赖注入机制将对象之间的依赖关系交由框架处理,减低组件的耦合性
- spring提供了面向切面(AOP)技术,支持将一些通用任务进行集中式管理。从而提供更好的复用
- spring对主流的应用框架提供了集成支持;属于低侵入式设计,代码的污染低;

## 3、Spring的AOP理解

### 3.1、代理模式

- 定义:给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用;(中介)

- ![代ç模å¼ç UML å¾](https://www.runoob.com/wp-content/uploads/2014/08/proxy_pattern_uml_diagram.jpg)

- ```java
  //静态代理
  
  //接口
  public interface Image {
     void display();
  }
  //实现类
  public class RealImage implements Image {
   
     private String fileName;
   
     public RealImage(String fileName){
        this.fileName = fileName;
        loadFromDisk(fileName);
     }
   
     @Override
     public void display() {
        System.out.println("Displaying " + fileName);
     }
   
     private void loadFromDisk(String fileName){
        System.out.println("Loading " + fileName);
     }
  }
  //代理类,实现接口
  public class ProxyImage implements Image{
   
     private RealImage realImage;
     private String fileName;
   
     public ProxyImage(String fileName){
        this.fileName = fileName;
     }
   
     @Override
     public void display() {
        if(realImage == null){
           realImage = new RealImage(fileName);
        }
        realImage.display();
     }
  }
  
  //测试类
  public class ProxyPatternDemo {
     
     public static void main(String[] args) {
        Image image = new ProxyImage("test_10mb.jpg");
   
        // 图像将从磁盘加载
        image.display(); 
        System.out.println("");
        // 图像不需要从磁盘加载
        image.display();  
     }
  }
  ```

- 动态代理
  - 基于接口,JDK
    - 核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
  - 基于类,CGLIB

### 3.2、AOP

- OOP面向对象,允许开发者定义纵向的关系,定义横向的关系时会导致大量代码的重复,而不利于各个模块的重用

- AOP,面向切面,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块命名为”切面“,减少系统的重复代码。(日志、权限)

- 静态代理

  - AspectJ

- 动态代理

  - 代理模式的动态代理

- 实现方式

  - 使用原生Spring API接口

    - ```xml
       <aop:config>
      <!-- 切入点:方法在那里执行   expression:表达式,execution()-->
       <aop:pointcut id="pointcut" expression="execution(* com.duan.service.UserServiceImpl.*(..))"/>
      <!--        &lt;!&ndash;执行环绕增加!&ndash;&gt;-->
      <!--    将bean注册的类log,切入到pointcut的方法中-->
       <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
       </aop:config>
      
      ```

      

  - 自定义类

    - ```xml
      <bean id="diy" class="com.duan.diy.DiyPointCut"/>
      
          <aop:config>
              <!--自定义切面,ref:要引用的类-->
              <aop:aspect ref="diy">
                  <!--切入点-->
                  <aop:pointcut id="point" expression="exection(* com.duan.service.UserServiceImpl.*(..))"/>
                  <!--通知-->
                  <aop:before method="before" pointcut-ref="point"/>
                  <aop:after method="after" pointcut-ref="point"/>
              </aop:aspect>
          </aop:config>
      ```

  - 使用注解方式

    - @Aspect 标注类为一个切面
    - @Before ("execution(...)")标记方法,execution(需要切入的方法)标记切入点
    - @After,@Around

## 4、Spring 的IOC理解

#### 4.1、IOC

- IOC,控制反转,指创建对象的控制权的转移,手动创建对象的控制权交给spring框架管理
- 注入方式:构造器注入、setter方法注入、注解注入

## 5、BeanFactory和ApplicationContext的区别

- ApplicationContext是BeanFactory的子接口
- BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化
- ApplicationContext,它是在容器启动时,一次性创建了所有的Bean

## 6、Spring Bean的生命周期

- **实例化Bean**:对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。
- **设置对象属性(依赖注入)**:实例化后的对象被封装在BeanWrapper对象中
- **处理Aware接口:**

## 7、Spring常用注解

- @Autowired(标记在属性、成员上):自动装配
  - 消除代码里的getter/setter与bean属性的property;默认是按类型匹配;
- @Qualifier(标记在类上,指定注入Bean的名称)
  - 若有多个实现类,且都在xml配置文档中,可以使用@Qualifier(引用的实现类)
- @Resource(和Autowired功能类似,默认byName,找不到按byType;不属于Spring注解属于j2EE)
- @Service(业务层)/@Component(泛指组件)/@Controller(表现层)/@Repository(数据层)
  - 都放在类上使用,被标记的类,会被作为Bean注册进Spring容器。

## 8、Spring循环依赖

Spring解决循环依赖依靠的是Bean的“中间态”这个概念,而这个中间态指的是`已经实例化`,但还没初始化的状态。而构造器是完成实例化的东东,所以构造器的循环依赖无法解决~~~

```xml
https://blog.csdn.net/f641385712/article/details/92801300
```

- 解决办法:三级缓存

```java
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    ...
    // 从上至下 分表代表这“三级缓存”
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //一级缓存
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二级缓存
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三级缓存
    ...
    
    /** Names of beans that are currently in creation. */
    // 这个缓存也十分重要:它表示bean创建过程中都会在里面呆着~
    // 它在Bean开始创建时放值,创建完成时会将其移出~
    private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

    /** Names of beans that have already been created at least once. */
    // 当这个Bean被创建完成后,会标记为这个 注意:这里是set集合 不会重复
    // 至少被创建了一次的  都会放进这里~~~~
    private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
}
```

- `singletonObjects`:用于存放完全初始化好的 bean,**从该缓存中取出的 bean 可以直接使用**
- `earlySingletonObjects`:提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖
- `singletonFactories`:单例对象工厂的cache,存放 bean 工厂对象,用于解决循环依赖

获取单例bean的源码:

```java
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    ...
    @Override
    @Nullable
    public Object getSingleton(String beanName) {
        return getSingleton(beanName, true);
    }
    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }
    ...
    public boolean isSingletonCurrentlyInCreation(String beanName) {
        return this.singletonsCurrentlyInCreation.contains(beanName);
    }
    protected boolean isActuallyInCreation(String beanName) {
        return isSingletonCurrentlyInCreation(beanName);
    }
    ...
}
```

- 先从`一级缓存singletonObjects`中去获取。(如果获取到就直接return)
- 如果获取不到或者对象正在创建中(`isSingletonCurrentlyInCreation()`),那就再从`二级缓存earlySingletonObjects`中获取。(如果获取到就直接return)
- 如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过`getObject()`获取。就从`三级缓存singletonFactory`.getObject()获取。**(如果获取到了就从`singletonFactories`中移除,并且放进`earlySingletonObjects`。其实也就是从三级缓存`移动(是剪切、不是复制哦~)`到了二级缓存)**

## 9、Spring Bean创建过程

```xml
https://www.imooc.com/article/32882
```

![img](https://img-blog.csdnimg.cn/20190619142513115.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Y2NDEzODU3MTI=,size_16,color_FFFFFF,t_70)

# 二、Java总结

## 0、反射

- 在运行状态中,对于任何一个类都知道这个类的属性和方法,对于任何一个对象,都可以调用它的属性和方法。这种动态获取的信息以及动态调用对象方法的机制被称为反射。

## 1、重写、重载

- 重载是同一个方法能够根据输入数据的不同,做出不同的处理
  - 方法名相同,参数类型、个数、顺序,方法返回值和访问修饰符不同
  - 只是返回值不同不能算重载;
- 重写,发生在继承、子类对父类的方法重写

## 2、封装、继承、多态

- 多态
  - 程序定义的引用变量所指向的具体类型和通过引用调用的方法在编程时不确定,在程序运行期间才确定。
  - 实现方法
    - 接口实现,实现接口并实现方法
    - 继承,多个子类对同一方法重写

## 3、String、StringBuilder、StringBuffer

- String对象是不可变的,使用final关键字修饰字符数组来保存字符串
- StringBuilder、StringBuffer都继承自AbstractStringBuilder
- StringBuffer,String线程安全,StringBuilder线程不安全

## 4、接口、抽象类

- 抽象类可以有非抽象方法
- 一个类可实现多个接口,但只能实现一个抽象类
- 接口方法默认修饰符是public,抽象方法不可用private关键字修饰

## 5、final关键字

- 修饰变量
- 修饰方法
- 修饰类,类不能被继承、成员方法都会被隐式指定为final方法

## 6、深拷贝、浅拷贝

- 浅拷贝:对基本数据类型进行值传递,引用类型进行引用传递
- 深拷贝:基本数据类型进行值传递,对于引用数据类型,创建一个新的对象,并复制其内容

## 7、ArrayList、LinkedList、Vector

- 两者都不是线程安全的
- ArrayList底层是Object数组实现,LinkedList底层是双向链表
- ArrayList可支持随机访问
- Vector类的所有方法都是同步的,线程安全,耗时大

## 8、HashMap、HashTable

- HashMap线程不安全,HashTable线程安全(内部方法synchronized修饰)
- HashMap初始底层实现方法:数组+链表;当链表长度过长时,链表转化为红黑树
- HashMap的键值可为null,HashTable键值为null会抛出异常(NullPointerException)
- HashTable使用同一把锁保证线程安全、ConcurrentHashMap通过分段锁实现线程安全

## 9、HashSet检重

HashSet首先会先计算对象的hashcode值来判断对象位置,同时也会与其他加入的对象hashcode值作比较,不相同则认为没有重复,相同的话,调用equals()方法检查哈希值相同的对象是否相同;

## 10、集合框架底层数据结构

- List
  - ArrayList:Object数组
  - Vector:Object数组
  - LinkedList:双向链表
- Set
  - HashSet(无序,唯一);基于hashMap实现
  - LinkedHashSet:继承于HashSet,通过LinkedHashMap实现
  - TreeSet(有序,唯一):红黑树
- Map
  - HashMap:数组+链表/数组+红黑树
  - LinkedHashMap:
  - HashTable:数组+链表
  - TreeMap:红黑树

## 11、多线程

### 11.1线程、进程

进程是程序的一次执行过程,是系统运行程序的基本单位。线程是一个比进程更小的执行单位。**同类的多个线程共享进程的堆和方法区资源**,**但每个线程有自己的程序计数器和虚拟机栈、本地方法栈。**

### 11.2程序计数器

- 实现代码流程控制
- 多线程情况下,记录当前线程执行的位置,从而保证线程切换

### 11.3线程死锁

- 多个线程同时被阻塞,都在等待某个资源被释放
- 死锁条件
  - 互斥条件:资源只能有一个线程占用
  - 请求与保持条件:进程被阻塞时,所获得资源不释放
  - 不剥夺条件:资源在被一个进程占用时,其他进程不可强行使用
  - 循环等待:若干进程形成头尾相接的循环等待资源关系

### 11.4sleep()、wait()

- sleep方法没有释放锁,wait方法释放了锁
- 都可暂停线程的执行
- sleep调用后,线程可自然苏醒,wait方法不可需调用同一对象上的notify()或notifyAll()

### 11.5 synchronized,ReentrantLock关键字

- 修饰实例方法:作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
- 修饰静态方法:是给当前类加锁,会作用于类的所有对象实例,因为静态方法不属于任何一个实例对象,是类成员。
- 修饰代码块:指定加锁对象
- **synchronized关键字加到static静态方法和synchronized(class)代码块上都是给class类上锁。synchronized关键字加到实例方法上是给对象实例上锁。**

- 底层实现

  - 同步语句块:使用的是monitorenter和monitorexit指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令指明代码块结束位置。
  - 修饰方法:使用的ACC_SYNCHRONIZED标识,JVM同过该标识辨别方法是否声明为同步方法。

- 区别

  - 两者都是可重入锁

  - synchronized依赖于JVM,ReentrantLock依赖于API(JDK层面实现)

  - ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁

  - ReentrantLock实现等待/通知机制,需要借助于Condition接口和newCondition()方法。

    - 可以实现多路通知功能,也就是一个Lock对象中可以创建多个Condition实例(对象监视器),线程对象可以注册在指定Condition中,从而可以有选择性的进行线程通知,线程调度上更加灵活。

    - ```java
      /*
      线程A输出1 2 3,线程B输出4 5 6, 线程A输出7 8 9
       */
      public class ReentrantLock  {
          static class NumberWrapper{
              int val = 1;
          }
      
          public static void main(String[] args) {
              //初始化可重入锁
              final Lock lock = new java.util.concurrent.locks.ReentrantLock();
      
              //第一个条件当屏幕上输出到3
              final Condition condition3 = lock.newCondition();
      
              //第二个条件当屏幕上输出到6
              final  Condition condition6 = lock.newCondition();
      
              final NumberWrapper num = new NumberWrapper();
      
              //初始化线程A
              Thread threadA = new Thread(
                      new Runnable() {
                          public void run() {
                              //需要先获得锁
                              lock.lock();
                              try{
                                  System.out.println("线程A开始输出:");
                                  while(num.val <= 3)
                                  {
                                      System.out.println(num.val);
                                      num.val++;
                                  }
                                  condition3.signal();
                              }
                              finally {
                                  lock.unlock();
                              }
      
      
                              //输出7 8 9
                              lock.lock();
                              try{
                                  //等待输出
                                  condition6.await();
                                  System.out.println("线程A开始输出:");
                                  while(num.val <= 9)
                                  {
                                      System.out.println(num.val);
                                      num.val++;
                                  }
                              }
                              catch (InterruptedException ex){
                                  System.out.println(ex.getMessage());
                              }
                              finally {
                                  lock.unlock();
                              }
                          }
                      }
              );
      
              Thread threadB = new Thread(
                      new Runnable() {
                          public void run() {
                              //获得锁
                              lock.lock();
                              try{
                                  while(num.val <= 3)
                                      condition3.await();
      
                              }
                              catch (InterruptedException ex){
                                  System.out.println(ex.getMessage());
                              }
                              finally {
                                  lock.unlock();
                              }
      
                              try{
                                  lock.lock();
                                  //等待线程A输出完成,唤醒condition3
      
                                  System.out.println("线程B开始输出:");
                                  while(num.val <= 6){
                                      System.out.println(num.val);
                                      num.val++;
                                  }
                                  condition6.signal();
                              }
                              finally {
                                  lock.unlock();
                              }
                          }
                      }
              );
              threadB.start();
              threadA.start();
      
          }
      }
      ```

- synchronized 和Lock的区别
  - synchronized是java内置关键字,在jvm层面。Lock是java类
  - synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁。
  - synchronized会自动释放锁(线程执行完同步代码;线程执行过程中发生异常),Lock需要在finally里手工释放锁(unlock())
  - 使用synchronized关键字的线程,一个线程获得锁,另一个线程需要等待,一个线程阻塞,另一个线程会一直等待;Lock锁,如果一个线程尝试获取不到锁,线程可以不用一直等待就结束了(tryLock());
  - synchronized的锁可重入、不可中断、非公平,Lock锁可重入、可中断、公平
  - synchronized锁适合代码少量的同步问题,Lock锁适合大量同步的代码的问题。

### 11.6 Volatile关键字

- 保证变量的可见性
- 防止指令重排
- Volatile和synchronized区别
  - volatile关键字只能用于变量,轻量级实现
  - 多线程访问volatile关键字不会发生阻塞,不能保证数据的原子性。

### 11.7 并发编程三个特性

- 原子性
- 可见性
- 有序性

### 11.8 ThreadLocal

- ThreadLocal类主要解决的就是让每个线程绑定自己的值。保证每个线程有自己的专属本地变量

### 11.9 线程实现方式

- 继承Thread类,重写run方法(无返回值)

```java

public class ThreadDemo01 extends Thread{
    public ThreadDemo01(){
        //编写子类的构造方法,可缺省
    }
    public void run(){
        //编写自己的线程代码
 System.out.println(Thread.currentThread().getName());
    }
    public static void main(String[] args){ 
        ThreadDemo01 threadDemo01 = new ThreadDemo01(); 
        threadDemo01.setName("我是自定义的线程1");
        threadDemo01.start();       
        System.out.println(Thread.currentThread().toString());  
    }
}
```

- 实现Runnable接口,重写run方法(无返回值),接口的实例作为参数传入带参的Thread构造参数

```java
public class ThreadDemo02 {
 
    public static void main(String[] args){ 
        System.out.println(Thread.currentThread().getName());
        Thread t1 = new Thread(new MyThread());
        t1.start(); 
    }
}
 
class MyThread implements Runnable{
    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println(Thread.currentThread().getName()+"-->我是通过实现接口的线程实现方式!");
    }   

```

- 通过Callable和FutureTask创建线程
  - 创建Callable接口的实现类,重写Call方法(有返回值)
  - 创建Callable实现类的对象,使用FutureTask类包装此对象
  - 将FutureTask作为参数传递给Thread构造方法创建线程

```java

public class ThreadDemo03 {
 
    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
 
        Callable<Object> oneCallable = new Tickets<Object>();
        FutureTask<Object> oneTask = new FutureTask<Object>(oneCallable);
 
        Thread t = new Thread(oneTask);
 
        System.out.println(Thread.currentThread().getName());
 
        t.start();
 
    }
 
}
 
class Tickets<Object> implements Callable<Object>{
 
    //重写call方法
    @Override
    public Object call() throws Exception {
        // TODO Auto-generated method stub
        System.out.println(Thread.currentThread().getName()+"-->我是通过实现Callable接口通过FutureTask包装器来实现的线程");
        return null;
    }   
}
```

- 通过线程池创建线程

```java
public class ThreadDemo05{
 
    private static int POOL_NUM = 10;     //线程池数量
 
    /**
     * @param args
     * @throws InterruptedException 
     */
    public static void main(String[] args) throws InterruptedException {
        // TODO Auto-generated method stub
        ExecutorService executorService = Executors.newFixedThreadPool(5);  
        for(int i = 0; i<POOL_NUM; i++)  
        {  
            RunnableThread thread = new RunnableThread();
 
            //Thread.sleep(1000);
            executorService.execute(thread);  
        }
        //关闭线程池
        executorService.shutdown(); 
    }   
 
}
 
class RunnableThread implements Runnable  
{     
    @Override
    public void run()  
    {  
        System.out.println("通过线程池方式创建的线程:" + Thread.currentThread().getName() + " ");  
 
    }  

```

### 11.10 进程间的通信

- 指在不同进程之间传播或交换信息
- 1 管道
  - 匿名管道
    - 在内核中申请一块固定大小的缓冲区,程序拥有写入和读取的权力,一般使用fork函数实现父子进程通信。
  - 命名管道
    - 在内核中申请一块固定大小的缓冲区,程序拥有写入和读取的权力,没有血缘关系的进程也可以进程间通信。
  - 特点:
    - 半双工(数据只能在一个方向上流动),具有固定的读端和写端。
    - 自带同步互斥机制
    - 面向字节流
- 2 消息队列
  - 消息队列是消息的链表,是存放在内核中并由消息队列标识符标识。
  - 特点
    - 消息队列允许一个或多个进程向它写入与读取消息
    - 管道与消息队列的通信数据都是先进先出的原则
    - 可以实现双向通信,生命周期随内核
- 3 信号量
  - 在内核中创建一个信号量集合(本质是个数组),数组的元素(信号量)都是1,使用P操作进行-1,使用V操作+1.
  - PV操作作用于同一进程,实现互斥;作用于不同进程,实现同步。
  - 功能:对临界资源进行保护
- 4 共享内存
  - 将同一块物理内存映射到不同的进程的虚拟地址空间中,实现不同进程间对同一资源的共享;是最有用的进程间通信方式,也是最快的进程通信方式。
  - 特点:
    - 不用从用户态到内核态的频繁切换和拷贝数据,直接从内存中读取就可以。
    - 共享内存是临界资源,所以需要操作时必须要保证原子性。使用信号量或者互斥锁都可以。
    - 生命周期随内核。

### 11.11 线程间的通信

- 1 同步
  - 通过synchronized关键字方法实现线程间的通信
-  2 while轮询的方法
-  3 wait/notify机制
-  4 管道通信

### 11.12 主线程等待所有子线程执行完成再执行

- 等待多线程完成的CountDownLatch

```java
public static void main(String[] args) throws InterruptedException {
        final CountDownLatch latch = new CountDownLatch(5);
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                public void run() {
                    try{
                        Thread.sleep(1000);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    latch.countDown();
                    System.out.println("子线程执行");
                }
            }).start();
        }
        //Thread.sleep(10000);
        latch.wait();
        System.out.println("主线程执行!");
    }
```

- sleep

```java
 public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            new Thread(){
                @Override
                public void run() {
                    try{
                        Thread.sleep(1000);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    System.out.println("子线程执行");
                }
            }.start();
        }
        Thread.sleep(10000);
        System.out.println("主线程执行!");
    }
```

### 11.13 多线程锁的升级原理

- Java线程阻塞的代价
  
- java 的线程是映射到原生操作系统线程上的,阻塞和唤醒操作系统都需要操作系统介入的,需要在用户态和核心态之间转换。这种切换会耗费大量操作系统资源,因为用户态和核心态都有各自专用的内存空间、寄存器等。用户态切换到内核态需要传递许多变量、参数给内核,内核也需要保存好用户态在切换时的寄存器值和变量。
  
- 锁的状态:无锁状态、偏向锁、轻量级锁、重量级锁

- 偏向锁

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

- 轻量级锁(自旋锁):锁标志位00

  - 轻量级锁是由偏向所升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁(原持有偏向锁的线程状态是活动状态)

  - 为什么引入偏向锁

    - 轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要CPU从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋这等待锁释放。

  - 线程1获取轻量级锁时会先把锁对象的**对象头MarkWord复制一份到线程1的栈帧中创建的用于存储锁记录的空间**(称为DisplacedMarkWord),然后**使用CAS把对象头中的内容替换为线程1存储的锁记录(**DisplacedMarkWord**)的地址**;

    如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,**线程2的CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁**。

    但是如果自旋的时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,比如10次或者100次,如果**自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。**

- 重量级锁

  - 轻量级锁膨胀之后,就升级为重量级锁了。
    重量级锁是依赖对象内部的monitor锁来实现的,而monitor又依赖操作系统的MutexLock(互斥锁)来实现的,所以重量级锁也被成为互斥锁。synchronized就是一个典型的重量级锁 

- 锁消除

- 锁粗化

## 12、线程池

### 12.1 线程池的创建

- 通过ThreadPoolExecutor构造方法实现
- 通过工具类Executors来实现
  - FixedThreadPool:返回一个固定线程数量的线程池
  - CachedThreadPool:返回一个可根据实际情况调整线程数量的线程池。线程数量不确定;
  - SingleThreadExecutor:返回一个只有一个线程的线程池

```java
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
 BlockingQueue<Runnable> workQueue) {
    .....
}

//corePoolSize:核心线程数,定义了最小可以同时运行的线程数量
//maximumPoolSize:线程池中最大线程数
//keepAliveTime:线程池空闲线程存活时间
//unit:keepAliveTime参数的时间单位
//workQueue:存放任务的队列,是阻塞队列

//handler(饱和策略):在队列(workQueue)和线程池达到最大线程数(maximumPoolSize)均满时仍有任务的情况下的处理方式
```
sleep 与 wait 区别
1. 对于 sleep()方法,我们首先要知道该方法是属于 Thread 类中的。而 wait()方法,则是属于 Object
类中的。
2. sleep()方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是他的监控状态依然保持
者,当指定的时间到了又会自动恢复运行状态。
3. 在调用 sleep()方法的过程中,线程不会释放对象锁。
4. 而当调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象
调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

start 与 run 区别
1. start()方法来启动线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕,可
以直接继续执行下面的代码。
2. 通过调用 Thread 类的 start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。
3. 方法 run()称为线程体,它包含了要执行的这个线程的内容,线程就进入了运行状态,开始运行
run 函数当中的代码。 Run 方法运行结束, 此线程终止。然后 CPU 再调度其它线程。

## 13、Atomic原子类

```java
// setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;
```

- AtomicInteger类主要利用CAS(compare and swap) + volatile关键字和native方法保证原子操作的
- CAS功能:判断内存中某个值是否与预期值相等,相等就用新值更新旧值,否则不更新。
- unsafe类的objectFieldOffset方法是本地方法,主要作用:拿到“原来的值”的内存地址。
- volatile关键字:保证value修改后后立马被其他线程感知到
- CAS实现原子操作的三大问题
  - ABA问题;解决办法:添加版本号
  - 循环时间长开销大;解决办法:pause指令
  - 只能保证一个共享变量的原子操作;解决办法:使用锁或者将变量合成一个使用

## 14、AQS(AbstractQueuedSynchronizer)

- 用来构建锁和同步器的框架。(ReentRantLock)
- 原理
  - 若请求的共享资源空闲,则将请求当前资源的线程设为有效的工作线程,并且将共享资源设定为锁定状态。若请求的共享资源被占有, AQS使用CLH队列锁机制,将暂时获取不到锁的线程加入到队列。

## 15、Java内存区域

![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAxNy85LzQvZGE3N2Q5MDE0Njc4NmMwY2IzZTE3MGI5YzkzNzZhZTQ_aW1hZ2VWaWV3Mi8wL3cvMTI4MC9oLzk2MC9mb3JtYXQvd2VicC9pZ25vcmUtZXJyb3IvMQ)

### 15.1 程序计数器(线程私有)

- 实现代码流程控制
- 多线程情况下,记录当前线程执行的位置,从而保证线程切换

### 15.2 栈(线程私有)

- 虚拟机栈
  - 为虚拟机执行Java方法服务
  - 存储基本类型、引用类型变量、方法
  - 线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会创建一个栈帧(Stack Frame)用于存储`局部变量表`、`操作数栈`、`动态链接`、`方法出口`等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。
- 本地方法栈
  - 为虚拟机使用到的Native方法服务

### 15.3 堆(线程共享)

- 存放对象实例,数组

### 15.4 方法区(线程共享)

- 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

### 15.5 常量池

- 存储文本字符串、被final关键字修饰的常量值以及符号引用

## 16、Java对象的创建过程

- 类加载检查
- 分配内存
  - 指针碰撞(内存规整情况下)
    - 用过的内存整合到一边,未用过的在另一边,中间有一个分界值指针
  - 空闲列表(内存不规整)
    - 虚拟机维护一个列表,用于记录哪些内存块是可用的,在分配的时候,找一块足够大的内存分配给对象实例即可
- 初始化零值
- 设置对象头
- 执行init方法

## 17、判断对象是否死亡

- 引用计数法
  - 给对象添加一个引用计数器,引用则计数器+1,引用失效计数器-1
- 可达性分析算法
  - 通过一系列“GC Roots”的对象作为起点,向下搜索,节点走过的路径称为引用链,当一个对象到GC Roots 没有任何引用链相连的话,则该对象是不可用的
  - GC Roots
    - Java虚拟机栈局部变量表中引用对象。
    - 本地方法栈JNI中引用的对象。
    - 方法区中类静态变量引用的对象。
    - 方法区中常量引用的对象。

## 18、垃圾收集算法

### 18.1 标记-清除算法

### 18.2 复制算法

### 18.3 标记-整理算法

### 18.4 分代收集算法

- 年轻代
  - 存活率低
  - 复制算法
- 老年代
  - 区域大,存活率高
  - 标记清除+整理算法

## 19、垃圾收集器

- Serial(串行)收集器
  - 单线程,即一条垃圾收集线程
- ParNew收集器
  - Serial收集器的多线程版本

.JVM参数
 在JVM启动参数中,可以设置跟内存、垃圾回收相关的一些参数设置,默认情况不做任何设置JVM会工
作的很好,但对一些配置很好的Server和具体的应用必须仔细调优才能获得最佳性能。通过设置我们希
望达到一些目标:
GC的时间足够的小
GC的次数足够的少
发生Full GC的周期足够的长

GC优化的目的有两个:
1、将转移到老年代的对象数量降低到最小;
2、减少full GC的执行时间;

## 20、JAVA容器

- 分为Collection和Map两大类;

- ![img](https://img-blog.csdnimg.cn/2019051914522990.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMDI2ODA5,size_16,color_FFFFFF,t_70)

- ![img](https://img-blog.csdnimg.cn/20190519152054986.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMDI2ODA5,size_16,color_FFFFFF,t_70)

- 线程安全的容器:HashTable,ConcurrentHashMap,Vector

- Collection和Collections的区别
  - Collection是一个集合接口,提供了对集合对象进行基本操作的通用接口方法,所有集合都是它的子类。
  - Collections是一个包装类,包含了很多静态方法,不能被实例化,就像一个工具类,比如提供的排序方法:Collection.sort()
  
- HashSet实现原理

  - 基于 HashMap 实现的,底层采用 HashMap 来保存元素

  ```java
  private transient HashMap<E,Object> map;
  
      // Dummy value to associate with an Object in the backing Map
      private static final Object PRESENT = new Object();
  
      /**
       * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
       * default initial capacity (16) and load factor (0.75).
       */
      public HashSet() {
          map = new HashMap<>();
      }
  ```


- 集合为什么要有迭代器(iterator)
  - 可以不用了解集合内部的数据结构,直接遍历
  - 不暴露内部数据,可以直接外部遍历
  - 适用性强,基本上的集合都可以使用。

## 21、JAVA对象的克隆

- 深克隆、浅克隆

### 浅克隆实现

- 实现Cloneable接口
- 重写clone方法,调用父类的clone方法

### 深克隆实现

- 1 递归调用clone方法
- 2 通过序列化对象,实现Serializable接口

## 22、反射

- 在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

- 获取Class对象的三种方法

  - 对象.getClass():

  ```java
  //第一种方式获取Class对象  
          Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
          Class stuClass = stu1.getClass();//获取Class对象
          System.out.println(stuClass.getName());
  
  ```

  - 任何数据类型都有一个“静态”的class属性:类名.class

  ```java
  //第二种方式获取Class对象
          Class stuClass2 = Student.class;
          System.out.println(stuClass == stuClass2);
  ```

  - 通过Class类的静态方法:forName()

  ```java
  Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
  ```

## 23、类的加载过程

- ![img](https://img-blog.csdnimg.cn/2019062014564165.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poYW9jdWl0,size_16,color_FFFFFF,t_70)

- 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

- 加载

  - 通过一个类的全限定名获取定义此类的二进制字节流。
  - 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  - 在内存中生成一个代表这个类的Class对象,作为方法区这个类的各种数据的入口。

- 验证

  - 确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。

- 准备

  - 正式为类变量分配内存并设置类变量(static修饰的变量)初始值的阶段。不包括实例变量。

  - 注:其次是这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量定义为:
    public static int value = 12;

    那么变量value在准备阶段过后的初始值为0而不是12,因为这时候尚未开始执行任何java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器()方法之中,所以把value赋值为12的动作将在初始化阶段才会被执行。

- 解析

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

- 初始化

## 24、字符串反转

- StringBuffer或StringBuilder的reverse方法
- 转换为char数组,重新拼接

## 25、IO流

![img](https://img-blog.csdn.net/20180127210359151)

- 字符流
  - inputStreamReader和outputStreamWriter
- 字节流
  - 继承inputStream和outputStream

## AIO、NIO、BIO

同步:发起一个调用后,被调用者未处理完请求之前,调用不返回;

异步:就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果;

阻塞:就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他事务,只有当条件就绪才能继续;

非阻塞:就是发起一个请求,调用者不用一直等着结果,可以先去干其他事情;

### 同步IO和异步IO

IO操作主要分为两个步骤,即发起IO请求和实际IO操作,同步IO与异步IO的区别就在于第二个步骤是否阻塞。

若实际IO操作阻塞请求进程,即请求进程需要等待或者轮询查看IO操作是否就绪,则为同步IO。

若实际IO操作并不阻塞请求进程,而是由操作系统来进行实际IO操作并将结果返回,则为异步IO。

### 阻塞IO和非阻塞IO

IO操作主要分为两个步骤,即发起IO请求和实际IO操作,阻塞IO与非阻塞IO的区别就在于第一个步骤是否阻塞。

若发起IO请求后请求线程一直等待实际IO操作完成,则为阻塞IO。

若发起IO请求后请求线程返回而不会一直等待,即为非阻塞IO。

- BIO:同步阻塞IO

  - 在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要对**每个请求建立一堆线程等待请求**,而客户端发送请求后,先咨询服务端是否有线程相应,如果没有则会一直等待或者遭到拒绝请求,如果有的话,客户端会线程会等待请求结束后才继续执行。
  - BIO方式适用于连接数目比较小且固定的架构

- NIO:同步非阻塞IO

  - ![img](https://img-blog.csdn.net/20180211163046147?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbWFkb25neXUxMjU5ODkyOTM2/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)

  - 多路复用器(seletor),是NIO编程的基础,非常重要,多路复用器提供选择已经就绪的任务的能力。

    简单说,就是Selector会不断地轮询注册在其上的通道(Channel),如果某个通道发生了读写操作,这个通道就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以取得就绪的Channel集合,从而进行后续的IO操作。

  - NIO方式适用于连接数目多且连接比较短的架构。

- AIO:异步非阻塞IO

  - NIO采用轮询的方式,一直在轮询的询问stream中数据是否准备就绪,如果准备就绪发起处理。但是AIO就不需要了,AIO框架在windows下使用windows IOCP技术,在Linux下使用epoll多路复用IO技术模拟异步IO, 即:应用程序向操作系统注册IO监听,然后继续做自己的事情。操作系统发生IO事件,并且准备好数据后,在主动通知应用程序,触发相应的函数(这就是一种以订阅者模式进行的改造)。由于应用程序不是“轮询”方式而是订阅-通知方式,所以不再需要selector轮询,由channel通道直接到操作系统注册监听。
  - AIO方式使用于连接数目多且连接比较长的架构。

- **阻塞和非阻塞** 强调的是程序在等待调用结果(消息,返回值)时的状态. 阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。 对于同步调用来说,很多时候当前线程还是激活的状态,只是从逻辑上当前函数没有返回而已,即同步等待时什么都不干,白白占用着资源。

- **同步和异步** 强调的是消息通信机制 (synchronous communication/ asynchronous communication)。所谓同步,就是在发出一个"调用"时,在没有得到结果之前,该“调用”就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由“调用者”主动等待这个“调用”的结果。而异步则是相反,"调用"在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在"调用"发出后,"被调用者"通过状态、通知来通知调用者,或通过回调函数处理这个调用

## 26、序列化

为了保存在内存中的各种对象的状态,并且可以把保存的对象状态在读出来。

什么情况下需要序列化

- 当需要把内存中的对象状态保存到一个文件中或数据库中的时候
- 使用套接字在网络上传送对象的时候
- 通过RMI(远端方法调用)传输对象的时候

## 27、常见异常类

- NullPointerEcception:访问空对象,抛出异常
- IndexOutOfBoundsException:索引超出范围
- NumberFormatException:将字符串转换为一种数值类型,但该字符串不能转换为适当格式,抛出异常
- IOException:发生I/O异常;此类是失败或中断的I/O操作生成的异常的通用类
- ClassCastException:将对象强制转换为不是实例的子类时。
- ArithmeticException:出现异常的运算条件;例如除以零;

## 28、JVM探究

### 28.1、类加载机制

- 类加载机制(双亲委派)
  - 启动类加载器
  - 扩展类加载器
  - 应用程序类加载器
  - 一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去完成加载。
  - 为什么要用双亲委派:任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间
    - 安全性:避免用户自己编写的类动态替换java的一些核心类,比如String
    - 避免了类的重复加载,因为jvm中区分不同类,不仅仅是根据类名,相同的class文件被不同的classLoader加载就是不同的两个类
- 类加载过程

### 28.2、内存结构

- 方法区
  - 线程共享
  - **静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区。**
- 栈
  - 线程私有,不存在垃圾回收问题
  - 每个方法执行都会创建一个栈桢,执行完毕出栈
  - 栈桢
    - 局部变量表(八大基本类型、对象引用--指向堆内存中对象)
    - 操作数栈
    - 动态链接
    - 方法出口
  - 程序正在执行的方法,一定在栈的顶部
  - StackOverFiowError(方法循环调用)
- 堆
  - 一个JVM只有一个堆内存,堆内存的大小是可以调节的
  - 线程共享
  - 存放对象实例
  - 细分三个区域
    - **新生代:**类的诞生和成长的地方
      - **伊甸园区:**所有对象都在在这个区被new出来
      - 存活区
        - s0区
        - s1区
    - 老年代
      - 养老区(old区)
    - 永久区
  - GC垃圾回收,主要是在伊甸园区和养老区
  - **OOM(java.lang.OutOfMemoryError: Java heap space),堆内存不够**
  - MAT和JProfiler工具分析OOM原因
  - -Xms1m -Xms8m -XX:+HeapDumpOnOutOfMemmoryError
    - -Xms 设置初始化内存分配大小
    - -Xmx  设置最大分配内存
    - -XX:+PrintGCDetails  打印GC垃圾回收信息
    - -XX:+HeapDumpOnOutOfMemmoryErro :OOM DUMP文件

```java
public class Mytest {
    @Test
    public void myTest(){
        String str = "dcfdcfdcfdcf";
        while(true){
            str += str + new Random(999999999) + new Random(999999999);
        }
    }
}
```

堆内存分配

![堆内存分配](C:\Users\lenovo\Desktop\堆内存分配.png)

堆+方法区+栈交互

https://note.youdao.com/yws/api/personal/file/WEB89d13aa2301bc7844bfb4a6cde5db647?method=getImage&version=415&cstk=Wo_iTo29

### 28.3、垃圾回收

- GC分类
  - 轻GC(普通的GC)
    - 新生区
    - 幸存区
  - 重GC(全局GC)
    - 老年代

## 29、JMM

- Java 内存模型(JMM)是一种抽象的概念,并不真实存在,它描述了一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段、静态字段和构成数组对象的元素)的访问方式。试图屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。

内存间交互操作

![img](https://oscimg.oschina.net/oscnet/536c6dfd-305a-4b95-b12c-28ca5e8aa043.png)

- read:把一个变量的值从主内存传输到工作内存中
- load:在 read 之后执行,把 read 得到的值放入工作内存的变量副本中
- use:把工作内存中一个变量的值传递给执行引擎
- assign:把一个从执行引擎接收到的值赋给工作内存的变量
- store:把工作内存的一个变量的值传送到主内存中
- write:在 store 之后执行,把 store 得到的值放入主内存的变量中
- lock:作用于主内存的变量
- unlock

# 三、数据库

## 1、数据库存储引擎

- 默认InnoDB,支持事务
- InnoDB和MyISAM区别
  - MyISAM只有表级锁(粒度最大,对整张表加锁),InnoDB支持行级锁(粒度最小,当前操作行加锁)、表级锁
  - 是否支持事务和崩溃后的安全恢复:MyISAM强调性能,执行速度快,不提供事物支持。InnoDB提供事物支持,外部键等高级数据库功能。
  - InnoDB支持外键,MyISAM不支持。
  - InnoDB支持MVCC

## 2、索引

- MySql索引使用的数据结构主要是BTree索引和哈希索引。哈希索引底层数据结构是哈希表,单条记录时,可选择哈希索引,查询性能快。

- 索引分类

  - 单值索引:一个索引只包含单个列,一个表可以有多个单列索引
  - 唯一索引:索引列的值必须唯一,但允许有空值
  - 复合索引:一个索引包含多个列

- Mysql索引结构

  - BTree索引
    - b+树:
      - 真实的数据存在于叶子节点
      - 非叶子节点不存储真实的数据,只存储指引搜索方向的数据项
  - Hash索引
  - full-text索引
  - R-Tree索引

- 哪些情况需要创建索引?

  - 1、主键自动创建唯一索引
  - 2、频繁作为查询的条件的字段创建索引
  - 3、查询中与其他表关联的字段,外键关系创建索引
  - 4、频繁更新的字段不适合创建索引(因为每次更新不单单是更新了记录,还会更新索引)
  - 5、where条件里用不到的字段不创建索引
  - 6、查询中统计或者分组字段(要分组先排序)

- 那些情况不需要创建索引

  - 表记录太少
  - 经常增删改的表
  - 数据重复且分布平均的表字段;

- 性能分析

  - **MySQL Query Optimizer**
    - Mysql中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供他认为最有的执行计划(他认为最优的数据检索方式)
    - **执行过程:**客户端向MySQL发送Query请求,命令解析器模块完成请求分类,把SELECT Query转发给MySQL Query Optimizer,MySQL Query Optimizer首先会对整条Query进行优化,进行常量表达式的预算,直接换算成常量值。并对Query中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。然后分析Query中的Hint信息(如果有),看Hint信息是否可以完全确定该Query的执行计划。如果没有Hint或Hint信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据Query进行相应的计算分析,最后得出执行计划

- Explain

  - 作用:
    - 表的读取顺序
    - 数据读取操作的操作类型
    - 那些索引可以使用
    - 那些索引被实际使用
    - 表之间的引用
    - 每张表有多少行被优化器查询
  - 表头

  https://note.youdao.com/yws/api/personal/file/WEB752f061c6e15d72de1697216deb35cfc?method=getImage&version=409&cstk=wC-sUEwA

  - 字段接释
    - **id:**表示查询中执行select子句或操作表的顺序
      - id相同,执行顺序由上至下(表)
      - id不同,如果是子查询,id的序号会递增,值越大优先级越高
    - **select_type:**查询的类型,主要用于区别普通查询、联合查询、子查询等复杂查询
      - SIMPLE:简单的select查询,不包含子查询或ubion
      - PRIMARY:若查询中包含任何复杂的子部分,最外层查询被标记为primary
      - SUBQUERY:在select或where列表中包含了子查询
      - DERIVED:在from列表中包含的子查询被标记为DERIVED(衍生),MySql会递归执行这些子查询,把结果放在临时表里
      - UNION:若第二个select出现在UNION之后,则被标记为UNION;若UNION包含在from子句的子查询中,外层select被标记为DERIVED
      - UNION RESULT:从union表中获取结果的select
    - **table:**显示这一行数据是关于哪一张表的
    - **type:**访问类型排列(显示查询使用了何种类型)
      - 最好到最差:system>const>eq_ref>ref>range>index>all
      - 一般来说,得保证查询至少达到range级别,最好达到ref级别
    - **possible_keys**
      - 显示可能应用在这张表中的索引,一个或多个。但不一定被实际使用
    - **key**
      - 实际使用的索引。如果为NULL,则没有使用索引
    - **key_len**
      - 表示索引中使用的字节数,可通过该列结算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好
    - **ref**
      - 显示索引的哪一列被使用了,如果可能的话,是一个常数,哪些列或常量被用于查找索引列上的值
    - **rows**
      - 根据表统计信息及索引选用情况,大致估算找出所需的记录所需要读取的行数
    - **Extra**

## 3、事务

- 事务是逻辑上的一组操作,要么都执行。要么都不执行。

### 3.1 事务特性

- 原子性:最小执行单位,不可分割
- 一致性:执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的。
- 隔离性:事务之间互不干扰
- 持久性:事务提交后,对数据库的改变时持久的。

### 3.2 并发事务的问题

- 脏读
  - 一个事务访问数据并修改了数据,但未提交,另一个事务访问了这个数据,然后使用了这个数据。因为这个数据是未提交的数据,所以另一个事务读取的是脏数据。
- 丢失修改(不可重复读的特殊情况)
  - 两个事务更新同一条数据资源,后完成的事务会造成先完成的事务更新丢失
- 不可重复读
  - 事务A对同一数据进行多次读取,但读取过程中,事务B修改了该数据,导致事务A读取结果不同。
- 幻读
  - A事务读取了两次数据,在读取过程中事务B添加了数据,导致A事务读取出的集合不一样。

#### 3.2.1 事务隔离界别

- 读取未提交
- 读取已提交
- 可重复读(**InnoDB默认的隔离级别,特殊:Next-Key Lock算法,可避免幻读**)
- 可串行化
- ![img](https://images2017.cnblogs.com/blog/679616/201801/679616-20180116184232756-1519061918.png)

## 4、大表优化

- 限定数据范围:禁止不带任何限制数据范围条件的查询语句。
- 读/写分离;主库负责写,从库负责读。
- 垂直分区:根据数据库里数据表的相关性进行拆分。即数据表列的拆分,把一张列比较多的表拆分为多张表。
- 水平分区:数据表的结构不变,通过某种策略存储分片。即数据表行的拆分。

## 5、Redis

### 5.1 Redis支持的数据类型

- String 字符串
- Hash 哈希;适合用于存储对象
- List 列表;
- Set 集合
- zset有序集合;关联了一个double类型的分数。redis通过此分数为集合中的成员进行从小到大的排序。

### 5.2 Redis持久化

- 把内存的数据写到磁盘中,防止服务器宕机内存数据丢失。

- 持久化的方式:RDB(默认)和AOF
  - RDB功能函数rdbSave(生成RDB文件)和rdbLoad(从文件加载到内存)
  - ![img](https://img2018.cnblogs.com/blog/1481291/201809/1481291-20180925141429889-1694430603.png)
  - AOF![img](https://img2018.cnblogs.com/blog/1481291/201809/1481291-20180925141527592-2105439510.png)

### 5.3、Redis那些架构模式

#### 5.3.1 哨兵

- 是一个分布式系统中监控redis主从服务器,并在主服务器下线时自动进行故障迁移
- 特性
  - 监控:哨兵会不断检查你的主服务器和从服务器是否运作正常。
  - 提醒:当被监控的某个Redis服务器出现问题时,哨兵可以通过API通知管理员或其他程序。
  - 自动故障迁移:当一个主服务器不能正常工作时,哨兵会开始一次自动故障迁移。

#### 5.3.4 集群

### 5.4、缓存穿透、缓存雪崩

#### 5.4.1 缓存穿透

- 一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就去数据库查找。一些恶意的请求会故意查询不存在的key值,请求量很大,就会对后端系统造成很大压力,这就是缓存穿透。
- 解决办法:
  - 对查询结果为空的情况也进行缓存,缓存的时间设置短一些,或者该key对应的数据insert了之后清理缓存。
  - 对一定不存在的key进行过滤。可以把所有可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。

#### 5.4.2 缓存雪崩

- 当缓存服务器重启或大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统造成很大压力。
- 解决办法
  - 缓存失效后,通过加锁或者队列来控制读取数据库写缓存的线程数量。
  - 做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1的失效时间设置为短期,A2设置为长期

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增
大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案
1.设置热点数据永远不过期。
2.加互斥锁,互斥锁缓存预热
### 5.5、Redis速度快的原因:

- 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。
- 数据结构简单
- 单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
- 使用多路I/O复用模型,非阻塞IO;

### 5.6、Redis过期策略

- 定时删除
- 懒汉式删除

## 6、Mysql查询在什么时候不走索引

- 在列上进行运算或使用函数会使索引失效。

- 前导模糊查询不会使用索引。

- 负向条件索引不会使用索引,建议用in。

  ```mysql
  !=、<>、not in、not exists、not like
  ```


## 7、Mysql慢查询排查

- 开启慢日志

```mysql
set global slow_query_log='ON'         -- 打开慢查询日志
set global slow_query_log_file='/usr/local/mysql/data/slow.log'; -- 慢查询日志存储位置
set global long_query_time=1;  -- 设置慢查询阈值
```

## 8、Mysql隔离级别设置

```mysql
show global variables like '%isolation%'    -- 查看隔离级别
set global transaction_isolation ='read-committed'; -- 设置隔离级别
```

## 9、Mysql查询是否使用索引

- Explain关键字查看是否使用索引

```mysql
explain select * from company_info where cname like '%小%'
```

![img](https://images2017.cnblogs.com/blog/1030607/201712/1030607-20171220092744209-1788352527.png)

```mysql
explain select * from company_info where cname like '小%'
```

![img](https://images2017.cnblogs.com/blog/1030607/201712/1030607-20171220092845865-712100965.png)

- mysql查看是否使用索引,简单的看type类型就可以。如果它是all,那说明这条查询语句遍历了所有的行,并没有使用到索引。

- **key:**

  ​     显示sql执行过程中实际使用的键或索引,如果为null则表示未使用任何索引,必须进行优化。

## 10、存储过程

## 11、乐观锁、悲观锁

- 悲观锁
  - 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其他线程阻塞,用完后再把资源转让给其他线程)。**传统的关系型数据库里面就用到了很多这种锁机制,比如行锁,表锁等。以及java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。**
- 乐观锁
  - 总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号和CAS算法实现。**乐观锁适用于多读的应用类型,这样可以提高吞吐量。**
  - 版本号机制:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改是,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,知道更新成功。
  - 乐观锁的缺点
    - ABA问题:一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,但这期间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。

## 12、聚簇索引、非聚簇索引

- 聚簇索引:将数据存储与索引放到了一起,索引结构的叶子节点保存了行数据
- 非聚簇索引:将数据与索引分开存储,索引结构的叶子节点指向了数据对应的位置。

# 四、数据结构

### 1、红黑树

- 每个节点非红即黑
- 根节点黑色
- 每个叶子节点都是黑色的空节点
- 如果节点是红色,其子节点必须是黑色
- 根节点到叶节点或空子节点的每条路径,黑色节点的数目必须相同

为了解决二叉查找树的缺陷

# 五、计算机网络

## 1、五层协议体系结构

- 应用层:
  - 通过应用进程间的交互来完成特定网络应用。应用层协议定义的是应用进程间的通信和交互规则。
  - 域名系统DNS,万维网的HTTP协议,支持电子邮件的SMTP协议等。
  - DNS:作为可以将域名和IP地址相互映射的一个分布式数据库,能够使人更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。
  - HTTP协议(超文本传输协议)
- 运输层:
  - 主要任务是负责向两台主机进程之间的通信提供通用的数据传输服务。
  - 传输控制协议TCP:面向连接的、可靠的数据传输服务。
  - 用户数据协议UDP:提供无连接的,尽最大努力的数据传输服务(不保证数据传输的可靠性)
- 网络层:
  - 计算机网络中进行通信的两个计算机之间可能会经过很多个数据链路,也可能还要经过很多通信子网。网络层的任务就是选择合适的网间路由和交换节点,确保数据及时传送。
  - IP、ICMP、IGMP
- 数据链路层:
  - 两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议。
- 物理层:
  - 实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异。

## 2、TCP三次握手

- ![img](https://img-blog.csdn.net/20180808105159546?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2p1bjIwMTY0MjU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
- 过程:
  - 客户端发送带有SYN标志的数据包给服务端(假设SYN序列号J)(客户端进入SYN_SENT状态)
  - 服务端收到SYN后,它会发送一个带有SYN、ACK标志的数据包给客户端。(ACK序号J+1、SYN是新的序列号为K)(服务端进入SYN_RECV状态)
  - 客户端收到新的SYN,ACK后,会回应一个序列号K+1的ACK的数据包(客户端、服务端进入ESTABLISHED)
- 目的:为了确认客户端服务端双发双收功能正常。
- 原因:通信双方相互告知序列号起始值,并确认对方已经收到了序列号起始值。

## 3、TCP四次挥手

- ![img](https://imgconvert.csdnimg.cn/aHR0cDovL2ltZy5ibG9nLmNzZG4ubmV0LzIwMTcwNjA3MjA1NzU2MjU1?x-oss-process=image/format,png)
- 四次握手主要是用来断开服务器和客户端之间的通信的。之所以要断开连接,是因为TCP/IP协议是要占用端口号的,而计算机的端口是有限的,不进行断开的话,势必会造成计算机资源的浪费。
- 过程:
  - 客户端数据传输到尾部时,客户端向服务器发送带有FIN标志的数据包(客户端进入FIN_WAIT_1 终止等待1状态)(序号为u)
  - 服务端收到客户端连接释放报文,发出确认报文ACK(序号u+1)。服务端进入CLOSE_WAIT(关闭等待)状态。
  - 客户端收到服务器的确认请求后,此时,客户端进入FIN_WAIT_2(终止等待2)状态,等待服务器发送连接释放报文。
  - 服务端将最后的数据发送完毕后,就向客户端发送释放报文FIN序号为w,服务器进入LAST_ACK(最后确认)状态。
  - 客户端收到服务器的连接释放报文后,发出确认,此时客户端进入TIME_WAIT(时间等待)状态。注意:此时TCP连接未释放,必须经过2*MSL(最大报文段生存时间)的时间后,客户端进入CLOSED状态;
  - 服务端收到客户端发出的确认后,立即进入CLOSED状态。
- 连接是三次握手而关闭是四次握手?
  - 因为当服务端收到客户端的SYN请求后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复ACK报文。当服务端的所有报文都发送完,才可发送FIN报文。
- 为什么TIME_WAIT状态需要经过2MSL才能返回到CLOSED状态?
  - 客户端发出最后的ACK回复,可能会丢失。服务端未收到ACK报文,将会重复发送FIN片段,所以客户端不能立即关闭,必须确认服务端接收到了ACK.
- 若以建立连接,客户端出现故障?
  - TCP设有一个保活计时器,客户端出现故障,服务端一直等待会浪费资源。计时器通常时间为两小时,两小时后服务端会发送探测报文。

## 4、TCP、UDP区别

- UDP在传送数据之前不需要先建立连接,远程主机收到UDP报文后,不需要给出任何确认。一般用于即时通信。比如:QQ语音,视频,直播等。
- TCP提供面向连接的服务。在传送数据之前需要先建立连接,数据传送后要释放连接,不提供广播或多播服务。一般用于文件传输,发送和接收邮件,远程登陆等。

## 5、浏览器输入url地址,显示主页的过程

- 浏览器查找域名的IP地址(DNS:获取域名对应IP)
  - DNS查找过程:浏览器缓存、路由器缓存、DNS缓存
- 浏览器向web服务器发送一个HTTP请求
- 服务器处理请求
- 服务器发回一个HTML响应
- 浏览器显示HTML

使用的协议:

- DNS:获取域名对应IP
- TCP协议:与服务器建立TCP连接
- IP协议:建立TCP协议时,需要发送数据,发送数据实在网络层使用IP协议
- OPSF协议:IP数据包在路由之间,路由选择使用OPSF协议
- ARP协议:路由器与服务器通信时,需要将IP地址转换为MAC地址
- HTTP协议:建立TCP后,使用HTTP协议访问网页

## 6、cooike/session

- cooike

cookie是服务器传给客户端的体积很小的纯文本文件。客户端请求服务器,如果服务器需要记录该用户状态,就向客户端浏览器发一个cookie。客户端浏览器会把cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该cookie一同提交给服务器。服务器检查该cookie,以此来辨认用户状态。

- session

session是另一种记录客户状态的机制,不同的是cookie保存在客户端浏览器中,而session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上,这就是session。客户端浏览器再次访问时只需要从该session中查找该客户的状态就可以了。**session相当于程序在服务器上建立的一份用户的档案,用户来访的时候只需要查询用户档案表就可以了。**

- 客户端禁止cookie不能实现session
  - 因为session使用sessionID来确定当前对话所对应的服务器session,而sessionID是通过cookie传递的。

## 7、解决应用服务器集群后的Session问题

- Session粘性

  - 通过负载均衡器能够根据每次请求的会话标识来进行请求转发,让同样的Session 的请求每次都发送到同一个服务器端处理,利于针对Session 进行服务器端本地的缓存。
  - 缺点:一台服务器宕机,会话数据全部丢失

  - ![img](https://img-blog.csdn.net/20150502103137899)

- Seesion复制

  - 在Web 服务器之间增加会话数据的同步,保证每个Web 服务器都有一样的Session
  - 缺点:同步Seesion会造成网络带宽的开销,只要Session有变化,就需要将数据同步到其他机器上。
  - ![img](https://img-blog.csdn.net/20150502103142688)

- Session 数据集中存储

  - 将Session数据存储在分布式缓存或数据库中(Redis)
  - 缺点:存储Session的机器或集群有问题时,影响我们的应用
  - ![img](https://img-blog.csdn.net/20150502103042864)

- cookie based

  - 该方案通过cookie来传递session数据,即把session数据存在cookie中
  - 缺点:cookie有长度限制,限制了session的长度;安全性问题,需要对session进行加密;性能影响。每次HTTP请求和响应都带有Session数据,对Web服务器来说,同样的处理情况下,响应的结果输出越少,支持的并发请求就会越多。
  - ![img](https://img-blog.csdn.net/20150502103046795)s

## 8、GET和POST两种基本请求方法的区别

- 1.get是从服务器上获取数据,post是向服务器传送数据。

- 2.get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到。post是通过HTTPpost机制,将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。

- 3.对于get方式,服务器端用Request.QueryString获取变量的值,对于post方式,服务器端用Request.Form获取提交的数据。

- 4.get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为100KB。(这里有看到其他文章介绍get和post的传送数据大小跟各个浏览器、操作系统以及服务器的限制有关)

- 5.get安全性非常低,post安全性较高。

# 六、设计模式

## 1、单例模式

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

- 单例类只有一个实例
- 单例类必须自己创建自己的唯一实例
- 单例类必须给其他对象提供这一实例
- 构造函数私有

```java
public class SingleObject {
 
   //创建 SingleObject 的一个对象
   private static SingleObject instance = new SingleObject();
 
   //让构造函数为 private,这样该类就不会被实例化
   private SingleObject(){}
 
   //获取唯一可用的对象
   public static SingleObject getInstance(){
      return instance;
   }
 
   public void showMessage(){
      System.out.println("Hello World!");
   }
}
```

- 懒汉式实现方式
  - 线程不安全

```java
public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}
```

- 线程安全的懒汉式

```java
public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}
```

- 饿汉式实现方式

  - 不是懒加载
  - 线程安全
  - 基于classloader机制避免了多线程的同步问题,不过,instance在类装载时就实例化。

  ```java
  public class Singleton{
      private static Singleton instance = new Singleton();
      private Singleton(){}
      public static Singleton getInstance(){
          return instance;
      }
  }
  ```

- 双重校验锁

  - 懒加载
  - 线程安全

  ```java
  public class Singleton{
      private volatile static Singleton instance;
      private Singleton(){}
      public static Singleton getInstance(){
          if(instance == null){
              synchronized(this.class){
                  if(instance == null)
                      instance = new Singleton();
              }
          }
          return instance;
      }
  }
  ```

  - 问题:为什么需要两次判断if(instance==null)?

      分析:第一次校验:由于单例模式只需要创建一次实例,如果后面再次调用getInstance方法时,则直接返回之前创建的实例,因此大部分时间不需要执行同步方法里面的代码,大大提高了性能。如果不加第一次校验的话,那跟上面的懒汉模式没什么区别,**每次都要去竞争锁。**

           第二次校验:如果没有第二次校验,假设线程t1执行了第一次校验后,判断为null,这时t2也获取了CPU执行权,也执行了第一次校验,判断也为null。接下来t2获得锁,创建实例。这时t1又获得CPU执行权,由于之前已经进行了第一次校验,结果为null(不会再次判断),获得锁后,直接创建实例。结果就会导致创建多个实例。所以需要在同步代码里面进行第二次校验,如果实例为空,则进行创建。

## 2、原型模式

- 浅克隆:引用类型只能克隆地址;
- 深克隆:引用类型也复制一份;

用于创建重复的对象,同时又能保  证性能。

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。

### Redis的五个常见使用场景
1、会话缓存(Session Cache)----string
最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?
幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。
2、全页缓存(FPC)
除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。
再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。
此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,
这个插件能帮助你以最快速度加载你曾浏览过的页面。
3、队列(秒杀,抢购,12306)list数据类型的使用-------list
Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。
如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。
4、排行榜/计数器(set数据类型的使用)-----set
Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:
当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:
ZRANGE user_scores 0 10 WITHSCORES
Agora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可以在这里看到。
5、发布/订阅
最后(但肯定不是最不重要的)是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统!(不,这是真的,你可以去核实)。

### MyBatis实现一对一,一对多有几种方式,怎么操作的?
有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap里面 的association,collection节点配置一对一,一
对多的类就可以完成
嵌套查询是先查一个表,根据这个表里面的结果的外键id,去再另外一个表里面查询数据, 也是通过配置association,collection,但另外
一个表的查询通过select节点配置


### 如何自定义注解实现功能
> 创建自定义注解和创建一个接口相似,但是注解的interface关键字需要以@符号开头。
> 注解方法不能带有参数;
> 注解方法返回值类型限定为:基本类型、String、Enums、Annotation或者是这些类型的数组;
> 注解方法可以有默认值;
> 注解本身能够包含元注解,元注解被用来注解其它注解。

#####################################################
1、如何解决分布式事务问题

https://zhuanlan.zhihu.com/p/183753774

2、@comdition注解

@Conditional是啥呀?
@Conditional注解是个什么东西呢,它可以根据代码中设置的条件装载不同的bean,比如说当一个接口有两个实现类时,我们要把这个接口交给Spring管理时通常会只选择实现其中一个实现类,这个时候我们总不能使用if-else吧,所以这个@Conditional的注解就出现了。

https://blog.csdn.net/xcy1193068639/article/details/81491071

3、myisam为什么比innodb查询快

InnoDB在做SELECT的时候,要维护的东西比MYISAM引擎多很多,影响查询速度有:

1)数据块,InnoDB要缓存,MyISAM只缓存索引块,  这中间还有换进换出的减少;

2)InnoDB寻址要映射到块,再到行,MyISAM记录的直接是文件的OFFSET,定位比InnoDB要快

3)InnoDB还需要维护MVCC一致; 虽然你的场景没有,但他还是需要去检查和维护

MVCC (Multi-Version Concurrency Control)多版本并发控制 。

4、线程池怎么用,超过核心线程数后怎样

刚开始都是在创建新的线程,达到核心线程数量5个后,新的任务进来后不再创建新的线程,而是将任
务加入工作队列,任务队列到达上线5个后,新的任务又会创建新的普通线程,直到达到线程池最大的线
程数量10个,后面的任务则根据配置的饱和策略来处理。

5、索引什么时候会失效

索引何时失效

    (1)组合索引未使用最左前缀,例如组合索引(A,B),where B=b不会使用索引;

    (2)like未使用最左前缀,where A like '%China';

    (3)搜索一个索引而在另一个索引上做order by,where A=a order by B,只使用A上的索引,因为查询只使用一个索引 ;

    (4)or会使索引失效。如果查询字段相同,也可以使用索引。例如where A=a1 or A=a2(生效),where A=a or B=b(失效)

    (5)如果列类型是字符串,要使用引号。例如where A='China',否则索引失效(会进行类型转换);

    (6)在索引列上的操作,函数(upper()等)、or、!=(<>)、not in等;

6、静态代理和动态代理有什么区别

https://zhuanlan.zhihu.com/p/159112639

7、分库分表如何切换数据源
https://blog.csdn.net/qq_39246466/article/details/121428123

8、Eureka的自我保护机制
在默认配置中,Eureka Server在默认90s没有得到客户端的心跳,则注销该实例,但是往往因为微服务跨进程调用,网络通信往往会面临着各种问题,比如微服务状态正常,但是因为网络分区故障时,Eureka Server注销服务实例则会让大部分微服务不可用,这很危险,因为服务明明没有问题。

为了解决这个问题,Eureka 有自我保护机制,通过在Eureka Server配置如下参数,可启动保护机制

eureka.server.enable-self-preservation=true
它的原理是,当Eureka Server节点在短时间内丢失过多的客户端时(可能发送了网络故障),那么这个节点将进入自我保护模式,不再注销任何微服务,当网络故障回复后,该节点会自动退出自我保护模式。

自我保护模式的架构哲学是宁可放过一个,决不可错杀一千

9、Innodb存储引擎是如何保证事务的ACID四个原则的
https://blog.csdn.net/wangyy130/article/details/105834114

https://blog.csdn.net/weixin_46487176/article/details/123367615?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_title~default-1.pc_relevant_paycolumn_v3&spm=1001.2101.3001.4242.2&utm_relevant_index=4

10、cas原理
https://blog.csdn.net/u011506543/article/details/82392338

11、什么是回表
https://www.cnblogs.com/ghoster/p/12510691.html


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值