Java基础

面向对象的三个特征:继承、封装和多态

封装:将某事物的属性和行为封装成一个对象。通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口

继承:子对象可以继承或拥有父对象的公有属性或行为。

多态:父对象的同一个行为在子对象中有不同的表现(重写)

​ 多态存在的三要素:有继承,有重写,有父类的引用指向子类对象

​ 作用:消除类型间的耦合性

final, finally, finalize 的区别

final:关键字,修饰符;表示变量不可改变,方法不可覆盖,类不可继承

finally:异常处理机制的一部分,finally结构总会执行,如数据库的关闭连接

finalize:GC回收对象时会对当前对象执行该方法(即回收时执行一些任务);定义在Object类中

Exception、Error、运行时异常与一般异常有何异同

同:都继承于Throwable类

异:

​ Error是系统内部错误和资源耗尽错误,正常情况下不大可能发生,无需捕获;

​ Exception是正常情况下可以预料的意外情况,应该捕获并处理

​ 运行时异常:属于非检查异常,不要求捕获处理

​ 一般异常:属于检查异常,编译异常,需要捕获处理,否则编译不通过

请写出5种常见到的runtime exception

IndexOutOfBoundsException(下标越界异常)
NullPointerException(空指针异常)
NumberFormatException (String转换为指定的数字类型异常)
ArithmeticException -(算术运算异常 如除数为0)
ArrayStoreException - (向数组中存放与声明类型不兼容对象异常)
SecurityException -(安全异常)

int 和 Integer 有什么区别,Integer的值缓存范围

int:八大基本数据类型的一种;默认值是0;无需实例化;直接存储值

Integer::int的包装类;默认值是null;需要实例化;实际是对象的引用,指向new 的Integer对象。

Integer的值缓存范围为-128~127

包装类,装箱和拆箱

包装类可以封装一个相应基本类型的数据,并提供一些有用的方法。

装箱:基本类型数据转换成包装类对象叫装箱;反之,

拆箱:包装类对象转换成基本类型数据叫拆箱。

其中自动装箱/拆箱是JDK1.5之后的特性,包装类和相应基本数据类型转换时系统自动执行。

String、StringBuilder、StringBuffer

String:不可变

StringBuilder:可变,线程不安全适合单线程,速度快

StringBuffer:可变,线程安全可以多线程,速度慢

重载和重写的区别

重载:

类以统一的方式处理不同类型的数据

方法名相同,但是参数类型、个数;返回值类型可以相同也可以不相同,不用作重载函数的区分;实现编译时多态

const 以及无const ,类似final

重写:

一般出现在继承关系中,子类对父类方法的重写,多态存在的三要素之一;实现运行时多态

抽象类和接口有什么区别

同:

都不能被实例化;

都可以被继承;

都可以包含声明;

派生类必须实现未实现的方法。

异:

抽象类可以有构造方法但接口不可以有;

抽象类可以有普通属性但接口默认被static final修饰;

抽象类可以有静态方法但接口不可以有;

类可以多实现单继承

说说反射的用途及实现

当程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言。我们认为 Java 并不是动态语言,但是它却有一个非常突出的动态相关的机制,俗称:反射。所以通过反射,我们可以运行时获取对象的类型信息。如工厂模式,代理模式。

主要用途是开发各种通用框架。

实现:

获取class对象

  • 1.根据类名获取Class.forName()
  • 2.根据对象获取object.getClass()
  • 3.根据类获取class.class()
  • 4.反序列化获取

用instanceOf判断对象是否为某个类的实例

用newInstance()方法创建实例

获取方法和属性

  • getDeclaredMethods()
  • getMethods()
  • getFieds
  • ...

用invoke()调用方法

说说自定义注解的场景及实现

登陆、权限拦截、日志处理,以及各种 Java 框架都有注解的使用

实现:

程序运行时通过反射获取注解信息,并调用相应AOP处理该注解

HTTP请求的GET与POST方式的区别

get:

获取服务器数据,数据可见所以不安全

传输的数据小

仅支持ASCII字符

post:

向服务器上传数据,数据不可见较安全

传输的数据大,文件通常使用post传输

支持ISO10646

Session与Cookie区别

cookie是客户端存储,不安全,不会增加服务器压力

session是服务器存储

列出自己常用的JDK包

java.lang;java.io;java.sql;java.util;

MVC设计思想

MVC是model、view、controller的简称;

模型:业务逻辑包括业务数据的处理和加工以及相应的基础服务

视图:展示模型处理的结果;提供用户操作界面

控制器:用户操作视图发请求给控制器,控制器选择相应的模型处理,模型返回数据给控制器,控制器选择相应的视图展示给用户

优点:

低耦合,快速开发,代码复用性高,维护简单

缺点:

代码量大,前期成本高

equals与==的区别

==:

基本数据类型是值比较

引用类型是指向的对象的地址比较

equals:

重写后是值比较

未重写是指向的对象的地址比较

hashCode和equals方法的区别与联系

equals(Object obj)方法用来判断两个对象是否“相同”,如果“相同”则返回true,否则返回false。

hashCode()方法返回一个int数,在Object类中的默认实现是“将该对象的内部地址转换成一个整数返回”

若重写了equals(Object obj)方法,则有必要重写hashCode()方法。若两个对象equals(Object obj)返回true,则hashCode()有必要也返回相同的int数。

若两个对象equals(Object obj)返回false,则hashCode()不一定返回不同的int数。

若两个对象hashCode()返回相同int数,则equals(Object obj)不一定返回true。

若两个对象hashCode()返回不同int数,则equals(Object obj)一定返回false。

什么是Java序列化和反序列化,如何实现Java序列化?或者请解释Serializable 接口的作用

把对象转换为字节序列的过程称为对象的序列化,

把字节序列恢复为对象的过程称为对象的反序列化

序列化:

需要序列化的类要实现Serialzable接口;

用FileOutputStream构建一个ObjectOutputStream,使用对象输出流的writeObject()方法写出对象为字节序列。

Serialzable接口标注该类可被序列化。

Object类中常见的方法,为什么wait notify会放在Object里边?

这些方法存在于同步中,这些方法标识同步所属的锁,锁可以是任意对象,所以这些方法需要定义在Object类中

Java的平台无关性如何体现出来的

java程序可以运行在任意有jre环境的平台上。

wait(),sleep()区别

wait:释放资源,释放锁

sleep:释放资源,不释放锁

JDK和JRE的区别

JDK:

jdk是编写java程序的开发工具包,包含jre

JRE:

可以运行java程序

Java 8有哪些新特性

Lambda表达式;函数式接口;Optional解决null异常;Stream流处理数据。

Java常见集合

List 和 Set 区别

List和Set都是Collection的子类

List

  • 元素可重复
  • 可插入多个null元素
  • 有序
  • 实现类:ArrayList,LinkedList,Vector

Set

  • 元素不可重复
  • 仅可插入一个null元素
  • 无序
  • 实现类:HashSet,LinkedHashSet,TreeSet

Set和hashCode以及equals方法的联系

Set集合对元素唯一性的判断是通过元素所在类的重写方法hashCode和equals方法确定的;

先判断hashCode,后判断equals

List 和 Map 区别

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

Arraylist 与 LinkedList 区别

ArrayList是Array(动态数组)的数据结构,LinkedList是Link(链表)的数据结构。

数组容易随机访问,链表容易增加删除

数组占用连续的内存,链表不是占用连续的内存

ArrayList 与 Vector 区别

ArrayList是非线程安全的;Vector是线程安全的,但效率低

ArrayList是1.5倍扩容;Vector是2倍扩容,可以设置增长大小

HashMap 和 Hashtable 的区别

  • 实现原理相同,功能相同,底层都是哈希表结构,查询速度快
  • HashMap 继承AbstractMap类,Hashtable继承Dictionary类
  • HashMap是线程不安全的,Hashtable是线程安全的
  • HashMap允许一个null键或者多个null值,Hashtable不允许键值为null
  • HashMap重算hash,
#HashMap
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }    
int index = (n - 1) & hash
#Hashtable
int index = (hash & 0x7FFFFFFF) % tab.length;    
  • HashMap废弃了contains方法(功能与containsValue一致)
  • HashMap初始容量为16,扩容为2倍增长且要求为2的整数次幂;Hashtable初始容量为11,扩容为2倍+1增长

HashSet 和 HashMap 区别

HashSet 实现 Set接口,唯一且非空;HashMap 实现 Map接口,可空

HashSet仅存储对象,HashMap存储键值

HashSet根据对象重写的hashCode和equals方法判断唯一,HashMap根据键计算索引

HashMap 和 ConcurrentHashMap 的区别

ConcurrentHashMap是Hashtable的优化,是分段的哈希表,引入Segment可重入锁

img

HashMap 的工作原理及代码实现,什么时候用到红黑树

多线程情况下HashMap死循环的问题

当出现多线程操作导致扩容时,多线程的rehash操作容易导致链表闭合即出现环形链表,使得其死循环rehash

HashMap出现Hash DOS攻击的问题

ConcurrentHashMap 的工作原理及代码实现,如何统计所有的元素个数

手写简单的HashMap

看过那些Java集合类的源码

进程和线程

线程和进程的概念、并行和并发的概念

进程

程序是指令、数据及其组织形式的描述

进程是程序的实体,是系统进行资源分配和调度的基本单位

进程是线程的容器

线程

程序执行流的最小单位

是程序中一个单一的顺序控制流程

单个程序中同时运行多个线程完成不同的工作叫做多线程

并行

每个时间段有多个线程同时执行

并发

每个时间段只有一个线程执行

创建线程的方式及实现

继承Thread类创建线程

//继承Thread类
public class MyThread extends Thread{
  public void run(){
      //重写run方法
  }
}

public class Main {
  public static void main(String[] args){
         //创建并启动线程
    new MyThread().start();
  }
}

实现Runnable接口创建线程

//实现Runnable接口
public class MyThread2 implements Runnable {
  public void run(){
          //重写run方法
  }
}

public class Main {
  public static void main(String[] args){
    //创建并启动线程
    MyThread2 myThread=new MyThread2();
    Thread thread=new Thread(myThread);
    thread().start();
    //或者    new Thread(new MyThread2()).start();
  }
}

使用Callable和Future创建线程

//实现Callable接口
public class CallableDemo implements Callable<String> {

    @Override
    public String call() throws Exception {
        //重新call()方法
    }

       public static void main(String[] args) throws InterruptedException, ExecutionException {
        CallableDemo demoA = new CallableDemo("A");
        FutureTask<String> futureTaskA = new FutureTask<>(demoA);
        Thread threadA = new Thread(futureTaskA);
        threadA.start();
        threadB.start();
        System.out.println(futureTaskA.get());
    }

}

使用线程池创建线程

进程间通信的方式

无名管道

半双工通信方式,数据单项流动

在具有亲缘关系的进程间使用,如父子进程

高级管道

使用在父子进程中,即另一个程序的进程在当前程序进程中启动

有名管道

半双工通信方式,数据单项流动

允许无亲缘关系的进程间使用

消息队列

消息的链表

克服信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限问题

信号量

是一种计数器,用于控制多进程对共享资源的访问

信号

较复杂的通信方式,通知接受进程某个事件已经发生

共享内存

映射一段能被其他进程访问的内存叫做共享内存

该共享内存由一个进程创建,但可以被其他进程访问

套接字

可用于不同设备间的通信机制

说说 CountDownLatch、CyclicBarrier 原理和区别

CountDownLatch

await方法阻塞外部进程,当计数为0时执行外部进程

主进程要等子进程全部执行完了才能执行

只能执行一次

CyclicBarrier

await方法锁的是子进程,当计数达到阈值时执行主进程

达到阈值时同时计数置为0

可以多次达到阈值并执行任务

说说 Semaphore 原理

控制多个线程对同一个资源的访问

acquire获取许可访问资源,release释放资源

说说 Exchanger 原理

一对线程执行了exchange方法就交换它们的数据

ThreadLocal 原理分析,ThreadLocal为什么会出现OOM,出现的深层次原理

一种私有化存储线程的变量

讲讲线程池的实现原理

线程池的几种实现方式

线程的生命周期,状态是如何转移的

锁机制

说说线程安全问题,什么是线程安全,如何保证线程安全

可见性、顺序性、原子性

重入锁的概念,重入锁为什么可以防止死锁

所谓重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的

可重入锁的意义在于防止死锁
实现原理实现是通过为每个锁关联一个请求计数和一个占有它的线程。
当计数为0时,认为锁是未被占有的。线程请求一个未被占有的锁时,JVM则记录锁的占有者,并且讲请求计数器置为1 。
如果同一个线程再次请求这个锁,计数将递增;
每次占用线程退出同步块,计数器值将递减。直到计数器为0,锁被释放。

//子类覆写了父类的synchonized方法,然后调用父类中的方法,如果没有可重入锁,则产生死锁
public class Widget {
public synchronized void doSomething(){
  // do something
}
}
public class LoggingWidget extends Widget {
public synchronized void doSomething() {
  super.doSomething();
}
}
//为什么会产生死锁
/*
使用super关键字调用子类对象的父类方法,并没有生成一个父类对象去调用父类方法,而是用子类对象去调用子类中的父类方法。执行子类方法是获取了子类对象的锁,子类方法执行调用父类方法也**需要一个子类对象的锁,因为锁未释放所以死锁。
*/
public class Widget {
public synchronized void doSomething(){
  // do something
}
}
public class LoggingWidget extends Widget {
public synchronized void Widget.doSomething() {
  // do something
}// 父类的doSomething方法
public synchronized void doSomething() {
  super.doSomething();
}
}

产生死锁的四个必要条件(互斥、请求与保持、不剥夺、循环等待)

互斥:某个资源一段时间内只能一个进程占用,其他进程使用需要等待

请求与保持:某进程已经占用一份资源,还需要申请一份资源,但此资源被其他进程占用,请求阻塞但已占用资源不释放

不剥夺:进程占用的资源,其他进程不能强行占用,只能等待进程释放资源

循环等待:存在一种资源的循环等待链,当前进程等待下一进程的资源,上一进程等待当前进程的资源

如何检查死锁(通过jConsole检查死锁)

volatile 实现原理(禁止指令重排、刷新内存)

  1. 解决内存可见性原理

对于共享变量使用volatile修饰,当进程读取该共享变量的值时会刷新当前进程内存的共享变量缓存以获取最新的更新

  1. 禁止指令重排

为了优化程序性能,编译器和处理器会对Java编译后的字节码和机器指令进行重排序(即代码的执行顺序有所不同),可以保证单线程环境下的执行结果一致,但是多线程就会出现并发问题。如下例,在关键点处的代码在编译后的字节码指令可以如下表示:

  1. 为对象分配内存空间
  2. 初始化对象
  3. INSTANCE指向对象分配的内存地址

其中b,c执行顺序不影响单线程环境下的结果,但是多线程下则会由于某进程下c先与b执行,但在b执行前被另一进程使用,此时INSTANCE指向对象分配的内存地址但对象未初始化,且其不为空,则会返回一个未初始化的INSTANCE的单例对象。给INSTANCE加上volatile修饰则可禁止对volatile变量的读写操作重排序了。而编译出的字节码,也会在合适的地方插入内存屏障,比如volatile写操作之前和之后会分别插入一个StoreStore屏障和StoreLoad屏障,禁止CPU对指令的重排序越过这些屏障

对于volatile,JMM对最为保守的内存屏障插入规则如下:

  • 在每个volatile写操作之前插入一个StoreStore屏障
  • 在每个volatile写操作之后插入一个StoreLoad屏障
  • 在每个volatile读操作之后插入一个LoadLoad屏障
  • 在每个volatile读操作之后插入一个LoadStore屏障
//线程不安全的双重检查单例模式
public class Singleton{
    private static Singleton INSTACSE;
    private Singleton(){}
    public static Singleton getInstance(){
        if(INSTANCE == null){
            synchronized(Singleton.class){
                if(INSTANCE == null){
                    NSTANCE = new Singleton();//关键点
                }
            }
        }
    return INSTANCE;
  }
}

synchronized 实现原理(对象监视器)

JVM 是通过进入、退出 对象监视器(Monitor) 来实现对方法、同步块的同步的,而对象监视器的本质依赖于底层操作系统的 互斥锁(Mutex Lock) 实现

同步代码块是使用monitorenter和monitorexit指令实现的,同步方法依靠的是方法修饰符上的ACC_SYNCHRONIZED实现

synchronized 与 lock 的区别

  1. synchronized是一个关键字,Lock是一个接口
  2. synchronized异常会主动释放锁,Lock需要手动释放否则出现死锁
  3. Lock可以知道是否获取锁,synchronized不能
  4. synchronized:在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。
    普通同步方法,锁是当前实例对象

静态同步方法,锁是当前类的class对象
同步方法块,锁是括号里面的对象

Lock:一般使用ReentrantLock类做为锁。在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁

AQS同步队列

AbstractQueuedSynchronizer队列同步器

CAS无锁的概念、乐观锁和悲观锁

  1. 乐观锁-总以为不会发生并发问题,总是不上锁
  1. 悲观锁-总以为会发生并发问题,总是会上锁
  1. CAS-一种乐观锁

算法包含三个参数CAS(V,E,N): V表示要更新的变量,E表示期待的变量值,N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值

伪代码:

do{
备份旧数据;
基于旧数据构造新数据;
}while(!CAS( 内存地址,备份的旧数据,新数据 ))

即CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功完成操作。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。

常见的原子操作类

  1. 原子更新基本类型

AtomicBoolean: 原子更新布尔类型。
AtomicInteger: 原子更新整型。
AtomicLong: 原子更新长整型。

  1. 原子更新数组

AtomicIntegerArray: 原子更新整型数组里的元素。
AtomicLongArray: 原子更新长整型数组里的元素。
AtomicReferenceArray: 原子更新引用类型数组里的元素。

  1. 原子更新引用类型

AtomicReference: 原子更新引用类型。
AtomicReferenceFieldUpdater: 原子更新引用类型的字段。
AtomicMarkableReferce: 原子更新带有标记位的引用类型。

  1. 原子更新字段类

AtomicIntegerFieldUpdater: 原子更新整型的字段的更新器。
AtomicLongFieldUpdater: 原子更新长整型字段的更新器。
AtomicStampedFieldUpdater: 原子更新带有版本号的引用类型。
AtomicReferenceFieldUpdater: 原子更新引用类型的字段。

什么是ABA问题,出现ABA问题JDK是如何解决的

CAS无锁模式容易出现ABA问题,即某变量V的值初始为A,某段时间改为B,然后又改为A,那CAS操作就会认为该变量V从未修改

处理:java并发包中提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性。

乐观锁的业务场景及实现方式

Version机制:在数据表维护一个version字段,每次更新加1。线程读取数据时也会读取version值,若刚才读到的version值与当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

CAS算法:

适用于资源竞争少、线程冲突不严重的情况

Java 8并法包下常见的并发类

  1. ConcurrentHashMap
  2. CopyOnWriteArrayList
  3. CopyOnWriteArraySet
  4. ArrayBlockingQueue
  5. Atomic

偏向锁、轻量级锁、重量级锁、自旋锁的概念

这里写图片描述

JVM

JVM运行时内存区域划分

img

  • 方法区和堆是线程共享的,虚拟机栈、本地方法栈和程序计数器是每个线程私有的
  • 方法区:类信息、静态变量、常量、即时编译器编译后的代码
  • 堆:存放对象实例和数组
  • 虚拟机栈:描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表操作数栈动态链接方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。
  • 本地方法栈:虚拟机使用到的 Native 方法服务(C/C++代码)
  • 程序计数器:如果线程正在执行一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器的值则为 (Undefined);无OOM异常

内存溢出OOM和堆栈溢出SOE的示例及原因、如何排查与解决

如何判断对象是否可以回收或存活

常见的GC回收算法及其含义

常见的JVM性能监控和故障处理工具类:jps、jstat、jmap、jinfo、jconsole等

JVM如何设置参数

JVM性能调优

类加载器、双亲委派模型、一个类的生命周期、类是如何加载到JVM中的

类加载的过程:加载、验证、准备、解析、初始化

强引用、软引用、弱引用、虚引用

Java内存模型JMM

设计模式

常见的设计模式

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

设计模式的的六大原则及其含义

  1. 单一职责原则(Single Responsibility Principle,SRP)

一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。

  1. 开闭原则(Open Close Principle,OCP)

一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。

  1. 里氏代换原则(Liskov Substitution Principle,LSP)

所有引用基类(父类)的地方必须能透明地使用其子类的对象。

  1. 依赖倒置原则(Dependence Inversion Principle,DIP)

抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。

  1. 接口隔离原则(Interface Segregation Principle,ISP)

使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。

  1. 迪米特法则(Law oF Demeter,LoD)

一个软件实体应当尽可能少地与其他实体发生相互作用。

常见的单例模式以及各种实现方式的优缺点,哪一种最好,手写常见的单利模式

  1. 饿汉式

    //线程安全,但不是懒加载造成内存浪费
    public class SingleInstance{
        private static SingleInstance singleInstance = new SingleInstance();
        pirvate SingleInstance(){}
        public static SingleInstance getInstance(){
            return singleInstance;
        }
    }
  2. 懒汉式

    //线程不安全,使用懒加载
    public class SingleInstance{
        private static SingleInstance singleInstance;
        pirvate SingleInstance(){}
        public static SingleInstance getInstance(){
            if(singleInstance == null){
                singleInstance = new SingleInstance();
            }
            return singleInstance;
        }
    }
  3. 懒汉式:同步方法

    //线程安全,懒加载,但每次都需要同步效率低
    public class SingleInstance{
        private static SingleInstance singleInstance;
        pirvate SingleInstance(){}
        public static synchronized SingleInstance getInstance(){
            if(singleInstance == null){
                singleInstance = new SingleInstance();
            }
            return singleInstance;
        }
    }
  4. 懒汉式:同步代码块

    //线程不安全,使用懒加载
    public class SingleInstance{
        private static SingleInstance singleInstance;
        pirvate SingleInstance(){}
        public static SingleInstance getInstance(){
            if(singleInstance == null){
                synchronized (SingleInstance.class){
                    singleInstance = new SingleInstance();
                }
            }
            return singleInstance;
        }
    }
  5. 懒汉式:双重锁机制

    //线程不安全,使用懒加载
    public class SingleInstance{
        private static SingleInstance singleInstance;
        pirvate SingleInstance(){}
        public static SingleInstance getInstance(){
            if(singleInstance == null){
                synchronized (SingleInstance.class){
                    if(singleInstance == null){                
                        singleInstance = new SingleInstance();
                    }
                }
            }
            return singleInstance;
        }
    }
  6. 懒汉式:静态代码块

    //线程安全,延迟加载,效率高
    public class SingleInstance{
        private static SingleInstance singleInstance;
        pirvate SingleInstance(){}
        public static SingleInstance getInstance(){
            return Single.singleInstance;
        }
        private static class Single{
            private static SingleInstance ssingleInstance = new SingleInstance();
        }
    }

设计模式在实际场景中的应用

Spring中用到了哪些设计模式

  1. 工厂模式

Spring使用工厂模式可以通过 BeanFactoryApplicationContext 创建 bean 对象

<beans>
    <bean id="beadId-1" class="com.xxx.Xxx"> </bean>
    <bean id="beadId-2" class="com.xxx.Xxx"> </bean>
</beans>
  1. 单例模式

线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象都是单例的,bean的默认作用域是singleton。

<beans>
    <bean id="beadId-1" class="com.xxx.Xxx" scope="singleton"> </bean>
    <bean id="beadId-2" class="com.xxx.Xxx"> </bean>
</beans>
  1. 代理模式

Spring AOP就是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,Spring AOP会使用Cglib,这时候Spring AOP会使用Cglib生成一个被代理对象的子类来作为代理

  1. 模板方法

Spring 中 jdbcTemplatehibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式

  1. 观察者模式

Spring 事件驱动模型就是观察者模式很经典的一个应用。

  1. 适配器模式

Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller

  1. 装饰器模式

装饰者模式可以动态地给对象添加一些额外的属性或行为。

MyBatis中用到了哪些设计模式

你项目中有使用哪些设计模式

说说常用开源框架中设计模式使用分析

动态代理很重要!!!

数据结构

  • 树(二叉查找树、平衡二叉树、红黑树、B树、B+树)
  • 深度有限算法、广度优先算法
  • 克鲁斯卡尔算法、普林母算法、迪克拉斯算法
  • 什么是一致性Hash及其原理、Hash环问题
  • 常见的排序算法和查找算法:快排、折半查找、堆排序等

网络/IO基础

BIO、NIO、AIO的概念

BIO又被称为阻塞式I/O模型,那为什么会被叫做这个名字呢,原因就是BIO的特性是同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,处理完成后返回应答给客户端,也就是经典的请求-应答通信模型。但是随着客户端并发量上升,服务端的线程数膨胀,系统性能急剧下降,最终会导致系统不可用。这种模型无法满足高并发,高性能的场景。

既然BIO无法适应高并发情景,那么我们自然要想出一种方法来解决这种问题,那么NIO就出现了。即同步非阻塞IO。其实,NIO的本质就是通过通道和缓冲区来实现非阻塞。

AIO编程,在NIO基础之上引入了异步通道的概念。并提供异步文件和异步套接字通道的实现,从而在真正意义上实现了*异步非阻塞*,之前我们学过的NIO只是非阻塞而非异步。而AIO它不需要通过多路复用器对注册的通道的进行轮训操作即可实现异步读写,从而简化了NIO编程模型。也可以称为NIO2.0,这种模式才是真正的属于*异步非阻塞*的模型。

BIO:BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。

NIO:NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。

AIO:AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

什么是长连接和短连接

在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。

而从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加入这行代码:

Connection:keep-alive

在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。

HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。

Http1.0和2.0相比有什么区别

新的传输格式:2.0使用二进制格式,1.0依然使用基于文本格式

多路复用:连接共享,不同的request可以使用同一个连接传输(最后根据每个request上的id号组合成正常的请求)

header压缩:由于1.X中header带有大量的信息,并且得重复传输,2.0使用encoder来减少需要传输的hearder大小

服务端推送:同google的SPDUY(1.0的一种升级)一样

Https的基本概念

HTTPS (基于安全套接字层的超文本传输协议 或者是 HTTP over SSL) 是一个 Netscape 开发的 Web 协议。

你也可以说:HTTPS = HTTP + SSL

HTTPS 在 HTTP 应用层的基础上使用安全套接字层作为子层。

三次握手和四次挥手、为什么挥手需要四次

从浏览器中输入URL到页面加载的发生了什么?

  1. DNS查询:解析域名ip地址
  2. TCP连接:浏览器与ip地址所在服务器进行TCP连接
  3. 发送HTTP请求
  4. Server处理HTTP请求并返回HTTP报文
  5. 浏览器解析并render页面
  6. HTTP连接断开

数据库

MySQL 索引使用的注意事项

索引的优点

  • 大大加快数据的查询速度
  • 使用分组和排序进行数据查询时,可以显著减少查询时分组和排序的时间
  • 创建唯一索引,能够保证数据库表中每一行数据的唯一性
  • 在实现数据的参考完整性方面,可以加速表和表之间的连接

索引的缺点

  • 创建索引和维护索引需要消耗时间,并且随着数据量的增加,时间也会增加
  • 索引需要占据磁盘空间
  • 对数据表中的数据进行增加,修改,删除时,索引也要动态的维护,降低了维护的速度

创建索引的原则

  • 更新频繁的列不应设置索引
  • 数据量小的表不要使用索引(毕竟总共2页的文档,还要目录吗?)
  • 重复数据多的字段不应设为索引(比如性别,只有男和女,一般来说:重复的数据超过百分之15就不该建索引)
  • 首先应该考虑对where 和 order by 涉及的列上建立索引

DDL、DML、DCL分别指什么

DML(data manipulation language) 数据操纵语言

   就是我们经常用到的SELECT、UPDATE、INSERT、DELETE。主要用来对数据库的数据进行的一些操作。

DDL(data definition language) 数据定义语言

   就是我们在创建表时用到的一些SQL语句。例如:CREATE、ALTER、DROP等。DDL主要是用在定义表或者改变表的结构、数据类型、表之间的链接和约束等初始化操作上。

DCL(Data Control Language) 数据控制语言

   用来设置或者更改数据库用户角色权限等的语句,例如:grant、revoke语句。

explain命令

left join,right join,inner join

INNER JOIN 关键字在表中存在至少一个匹配时返回行。

img

LEFT JOIN 关键字从左表(table1)返回所有的行,即使右表(table2)中没有匹配。如果右表中没有匹配,则结果为 NULL。

img

RIGHT JOIN 关键字从右表(table2)返回所有的行,即使左表(table1)中没有匹配。如果左表中没有匹配,则结果为 NULL。

img

FULL OUTER JOIN 关键字只要左表(table1)和右表(table2)其中一个表中存在匹配,则返回行。FULL OUTER JOIN 关键字结合了 LEFT JOIN 和 RIGHT JOIN 的结果。

img

注:当左表或右表有记录但右表或左表无记录是,空记录用NULL代替

数据库事物ACID(原子性、一致性、隔离性、持久性)

ACID特性

数据库管理系统中事务(transaction)的四个特性(分析时根据首字母缩写依次解释):原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)

所谓事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。(执行单个逻辑功能的一组指令或操作称为事务)

原子性

原子性是指事务是一个不可再分割的工作单元,事务中的操作要么都发生,要么都不发生。

可采用“A向B转账”这个例子来说明解释

在DBMS中,默认情况下一条SQL就是一个单独事务,事务是自动提交的。只有显式的使用start transaction开启一个事务,才能将一个代码块放在事务中执行。

一致性

一致性是指在事务开始之前和事务结束以后数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性

如A给B转账,不论转账的事务操作是否成功,其两者的存款总额不变(这是业务逻辑的一致性,至于数据库关系约束的完整性就更好理解了)。

保障机制(也从两方面着手):数据库层面会在一个事务执行之前和之后,数据会符合你设置的约束唯一约束,外键约束,check约束等)和触发器设置;此外,数据库的内部数据结构(如 B 树索引或双向链表)都必须是正确的。业务的一致性一般由开发人员进行保证,亦可转移至数据库层面。

隔离性

多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。

在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据

事务最复杂问题都是由事务隔离性引起的。完全的隔离性是不现实的,完全的隔离性要求数据库同一时间只执行一条事务,这样会严重影响性能。

事物的隔离级别(读未提交、读已提交、可重复读、可序列化读)

持久性

这是最好理解的一个特性:持久性,意味着在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。(完成的事务是系统永久的部分,对系统的影响是永久性的,该修改即使出现致命的系统故障也将一直保持)

write ahead logging:SQL Server中使用了WAL(Write-Ahead Logging)技术来保证事务日志的ACID特性,在数据写入到数据库之前,先写入到日志,再将日志记录变更到存储器中。

事物的隔离级别(读未提交、读已提交、可重复读、可序列化读)

隔离级别脏读不可重复读幻读第一类丢失更新第二类丢失更新
READ UNCOMMITED允许允许允许不允许允许
READ COMMITTED不允许允许允许不允许允许
REPEATABLE READ不允许不允许允许不允许不允许
SERIALIZABLE不允许不允许不允许不允许不允许

事务隔离级别和数据访问的并发性是对立的,事务隔离级别越高并发性就越差。所以要根据具体的应用来确定合适的事务隔离级别,这个地方没有万能的原则。

脏读、幻读、不可重复读

脏读(Dirty Read):A事务读取B事务尚未提交的数据并在此基础上操作,而B事务执行回滚,那么A读取到的数据就是脏数据。

时间转账事务A取款事务B
T1 开始事务
T2开始事务
T3 查询账户余额为1000元
T4 取出500元余额修改为500元
T5查询账户余额为500元(脏读)
T6 撤销事务余额恢复为1000元
T7汇入100元把余额修改为600元
T8提交事务

不可重复读(Unrepeatable Read):事务A重新读取前面读取过的数据,发现该数据已经被另一个已提交的事务B修改过了。

时间转账事务A取款事务B
T1 开始事务
T2开始事务
T3 查询账户余额为1000元
T4查询账户余额为1000元
T5 取出100元修改余额为900元
T6 提交事务
T7查询账户余额为900元(不可重复读)

幻读(Phantom Read):事务A重新执行一个查询,返回一系列符合查询条件的行,发现其中插入了被事务B提交的行。

时间统计金额事务A转账事务B
T1 开始事务
T2开始事务
T3统计总存款为10000元
T4 新增一个存款账户存入100元
T5 提交事务
T6再次统计总存款为10100元(幻读)

第1类丢失更新:事务A撤销时,把已经提交的事务B的更新数据覆盖了。

时间取款事务A转账事务B
T1开始事务
T2 开始事务
T3查询账户余额为1000元
T4 查询账户余额为1000元
T5 汇入100元修改余额为1100元
T6 提交事务
T7取出100元将余额修改为900元
T8撤销事务
T9余额恢复为1000元(丢失更新)

第2类丢失更新:事务A覆盖事务B已经提交的数据,造成事务B所做的操作丢失。

时间转账事务A取款事务B
T1 开始事务
T2开始事务
T3 查询账户余额为1000元
T4查询账户余额为1000元
T5 取出100元将余额修改为900元
T6 提交事务
T7汇入100元将余额修改为1100元
T8提交事务
T9查询账户余额为1100元(丢失更新)

数据库的几大范式

第一范式:列不可再分

  • 每一列属性都是不可再分的属性值,确保每一列的原子性
  • 两列的属性相近或相似或一样,尽量合并属性一样的列,确保不产生冗余数据

第二范式:有主键且属性完全依赖主键

  • 有主键,非主键字段依赖主键。确保一个表只说明一个事物

第三范式:消除传递依赖,消除冗余

  • 非主键字段不能相互依赖。 每列都与主键有直接关系,不存在传递的依赖

数据库常见的命令

说说分库与分表设计

分库与分表带来的分布式困境与应对之策(如何解决分布式下的分库分表,全局表?)

说说 SQL 优化之道

MySQL遇到的死锁问题、如何排查与解决

存储引擎的 InnoDB与MyISAM区别,优缺点,使用场景

索引类别(B+树索引、全文索引、哈希索引)、索引的原理

什么是自适应哈希索引(AHI)

为什么要用 B+tree作为MySQL索引的数据结构

聚集索引与非聚集索引的区别

遇到过索引失效的情况没,什么时候可能会出现,如何解决

limit 20000 加载很慢怎么解决

如何选择合适的分布式主键方案

选择合适的数据存储方案

常见的几种分布式ID的设计方案

常见的数据库优化方案,在你的项目中数据库如何进行优化的

2.2、Redis

  • Redis 有哪些数据类型
  • Redis 内部结构
  • Redis 使用场景
  • Redis 持久化机制
  • Redis 集群方案与实现
  • Redis 为什么是单线程的?
  • 缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级
  • 使用缓存的合理性问题
  • Redis常见的回收策略

2.3、消息队列

  • 消息队列的使用场景
  • 消息的重发补偿解决思路
  • 消息的幂等性解决思路
  • 消息的堆积解决思路
  • 自己如何实现消息队列
  • 如何保证消息的有序性

三、开源框架和容器

3.1、SSM/Servlet

  • Servlet的生命周期
  • 转发与重定向的区别
  • BeanFactory 和 ApplicationContext 有什么区别
  • Spring Bean 的生命周期
  • Spring IOC 如何实现
  • Spring中Bean的作用域,默认的是哪一个
  • 说说 Spring AOP、Spring AOP 实现原理
  • 动态代理(CGLib 与 JDK)、优缺点、性能对比、如何选择
  • Spring 事务实现方式、事务的传播机制、默认的事务类别
  • Spring 事务底层原理
  • Spring事务失效(事务嵌套),JDK动态代理给Spring事务埋下的坑
  • 如何自定义注解实现功能
  • Spring MVC 运行流程
  • Spring MVC 启动流程
  • Spring 的单例实现原理
  • Spring 框架中用到了哪些设计模式
  • Spring 其他产品(Srping Boot、Spring Cloud、Spring Secuirity、Spring Data、Spring AMQP 等)
  • 有没有用到Spring Boot,Spring Boot的认识、原理
  • MyBatis的原理

3.2、Netty

  • 为什么选择 Netty
  • 说说业务中,Netty 的使用场景
  • 原生的 NIO 在 JDK 1.7 版本存在 epoll bug
  • 什么是TCP 粘包/拆包
  • TCP粘包/拆包的解决办法
  • Netty 线程模型
  • 说说 Netty 的零拷贝
  • Netty 内部执行流程
  • Netty 重连实现

3.3、Tomcat

  • Tomcat的基础架构(Server、Service、Connector、Container)
  • Tomcat如何加载Servlet的
  • Pipeline-Valve机制

四、分布式

4.1、Nginx

  • 请解释什么是C10K问题或者知道什么是C10K问题吗?
  • Nginx简介
  • 正向代理和反向代理.
  • Nginx几种常见的负载均衡策略
  • Nginx服务器上的Master和Worker进程分别是什么
  • 使用“反向代理服务器”的优点是什么?

4.2、分布式其他

  • 谈谈业务中使用分布式的场景
  • Session 分布式方案
  • Session 分布式处理
  • 分布式锁的应用场景、分布式锁的产生原因、基本概念
  • 分布是锁的常见解决方案
  • 分布式事务的常见解决方案
  • 集群与负载均衡的算法与实现
  • 说说分库与分表设计
  • 分库与分表带来的分布式困境与应对之策

4.3、Dubbo

  • 什么是Dubbo
  • 什么是RPC、如何实现RPC、RPC 的实现原理
  • Dubbo中的SPI是什么概念
  • Dubbo的基本原理、执行流程

五、微服务

5.1、微服务

  • 前后端分离是如何做的?
  • 微服务哪些框架
  • Spring Could的常见组件有哪些?
  • 领域驱动有了解吗?什么是领域驱动模型?充血模型、贫血模型
  • JWT有了解吗,什么是JWT
  • 你怎么理解 RESTful
  • 说说如何设计一个良好的 API
  • 如何理解 RESTful API 的幂等性
  • 如何保证接口的幂等性
  • 说说 CAP 定理、BASE 理论
  • 怎么考虑数据一致性问题
  • 说说最终一致性的实现方案
  • 微服务的优缺点
  • 微服务与 SOA 的区别
  • 如何拆分服务、水平分割、垂直分割
  • 如何应对微服务的链式调用异常
  • 如何快速追踪与定位问题
  • 如何保证微服务的安全、认证

5.2、安全问题

  • 如何防范常见的Web攻击、如何方式SQL注入
  • 服务端通信安全攻防
  • HTTPS原理剖析、降级攻击、HTTP与HTTPS的对比

5.3、性能优化

  • 性能指标有哪些
  • 如何发现性能瓶颈
  • 性能调优的常见手段
  • 说说你在项目中如何进行性能调优

六、其他

6.1、设计能力

  • 说说你在项目中使用过的UML图
  • 你如何考虑组件化、服务化、系统拆分
  • 秒杀场景如何设计

6.2、业务工程

  • 说说你的开发流程、如何进行自动化部署的
  • 你和团队是如何沟通的
  • 你如何进行代码评审
  • 说说你对技术与业务的理解
  • 说说你在项目中遇到感觉最难Bug,是如何解决的
  • 介绍一下工作中的一个你认为最有价值的项目,以及在这个过程中的角色、解决的问题、你觉得你们项目还有哪些不足的地方

6.3、软实力

  • 说说你的优缺点、亮点
  • 说说你最近在看什么书、什么博客、在研究什么新技术、再看那些开源项目的源代码
  • 说说你觉得最有意义的技术书籍
  • 工作之余做什么事情、平时是如何学习的,怎样提升自己的能力
  • 说说个人发展方向方面的思考
  • 说说你认为的服务端开发工程师应该具备哪些能力
  • 说说你认为的架构师是什么样的,架构师主要做什么
  • 如何看待加班的问题
Last modification:October 17th, 2020 at 10:51 am