蚂蚁三面准备笔记

Java 基础

  • 一、JAVA基础
    • 1、类加载过程和机制
    • 2、内部类
    • 3、Java 的NIO,AIO,BIO
    • 4、同步与阻塞的关系
    • 5、线程池
    • 6、单例模式创建方式
    • 7、concurrent包
      • 7.1、常见类
      • 7.2、sleep() 和 wait()
    • 8、ReentrantLock的公平锁和非公平锁
    • 9、synchronized
    • 10、HashMap、ConcurrentHashMap
  • 二、JAVA虚拟机
    • 1、引用类型
    • 2、垃圾回收 GC
  • 四、数据库
    • 1、快照读 (snapshot read)与当前读 (current read)
    • 2、radis为什么这么快
  • 五、linux
    • 1、进程调度算法
    • 2、开源协议
    • 3、开源软件
    • 4、查看端口号
  • 六、Java Spring
    • 1、解决循环依赖问题
  • 七、其他
    • 1、哈希算法
    • 2、分布式锁
  • 参考

一、JAVA基础

1、类加载过程和机制

  • 1、 加载
  • 2、验证
  • 3、准备(静态变量属性赋值,final会赋真实值,其他赋值为零)
  • 4、解析(符号引用替换成直接引用)
  • 5、初始化

双亲委派机制: 先调用父类加载器,父类加载失败后才自己尝试加载
作用:保证核心类不被更改,避免重复加载
蚂蚁三面准备笔记_第1张图片

  • AppClassLoader应用类加载器,负责加载开发者自己写的类
  • ExtClassLoader称为扩展类加载器,主要负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目录下的所有jar包或者由java.ext.dirs系统属性指定的jar包
  • BootstrapClassLoader称为启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等(这个加载器非java类,获取输出时会返回一个null)String.class就是这个加载器加载的

2、内部类

  • 内部类可以直接使用外部类的成员,即使是private成员。
  • 内部类可以再继承一个类,使得 java 的多继承机制变得完整
  • 内部类可以是private和package的
  • 内部类的参数默认为 final

3、Java 的NIO,AIO,BIO

BIO:同步阻塞(blocking-IO)

  • 原始io,jdk1.4前唯一的方式

NIO:同步非阻塞(non-blocking-IO)

  • 多路服务器轮询
  • 适用于连接比较多,且比较短的服务,如聊天服务器
  • buffer,channel、selector

AIO:异步非阻塞(synchronous-non-blocking-IO)

  • 适用于连接比较多,且比较长的应用,如相册服务器

4、同步与阻塞的关系

  • 举个通俗的例子:你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。

  • 你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。

5、线程池

  • shutdown:正在执行的执行完,结束在等待队列中的进程并返回
  • shutdownNow:暴力中断所有进程(同Stop

线程池大小设置:

  • IO密集型应用:设置为硬件核数的二倍
  • 计算密集型应用:设置为硬件核数+1(有很多说法,加不加都行)
  • 线程池新建时的参数
public ThreadPoolExecutor(int corePoolSize,  // 线程池的核心线程数
                          int maximumPoolSize, // 线程池的最大线程数
                          long keepAliveTime, // 当线程数大于核心时,多余的空闲线程等待新任务的存活时间。
                          TimeUnit unit, // keepAliveTime的时间单位
                          ThreadFactory threadFactory, // 线程工厂
                          BlockingQueue<Runnable> workQueue,// 用来储存等待执行任务的队列
                          RejectedExecutionHandler handler // 拒绝策略
                          ) 

6、单例模式创建方式

1、懒汉式,线程不安全

  • 原理:在get函数中判断是否已经创建过实例,没有才建立
  • 线程不安全,lazy loading
 public class Singleton {
       
    private static Singleton instance;  
    private Singleton (){
     }  
  
    public static Singleton getInstance() {
       
    if (instance == null) {
       
        instance = new Singleton();  
    }  
    return instance;  
    }  
}

2、懒汉式,线程安全

  • 原理:在get函数上加 synchronized
  • 线程安全,lazy loading
  • 效率很低
public class Singleton {
       
    private static Singleton instance;  
    private Singleton (){
     }  
    public static synchronized Singleton getInstance() {
       
    if (instance == null) {
       
        instance = new Singleton();  
    }  
    return instance;  
    }  
}

3、饿汉式

  • 原理:静态变量实现,在类加载的时候实例化一次
  • 线程安全,非lazy loading,浪费内存
public class Singleton {
       
    private static Singleton instance = new Singleton();  
    private Singleton (){
     }  
    public static Singleton getInstance() {
       
    return instance;  
    }  
}

4、双重校验锁(DCL,即 double-checked locking)

  • 原理:使用synchronized类锁 加上static变量
  • jdk1.5 起
  • lazy loading
  • 线程安全
public class Singleton {
       
    private volatile static Singleton singleton;  
    private Singleton (){
     }  
    public static Singleton getSingleton() {
       
    if (singleton == null) {
       
        synchronized (Singleton.class) {
       
        	if (singleton == null) {
       
            	singleton = new Singleton();  
        	}  
        }  
    }  
    return singleton;  
    }  
}

5、静态内部类

  • 线程安全,懒加载,
public class Singleton {
       
    private static class SingletonHolder {
       
    	private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){
     }  
    public static final Singleton getInstance() {
       
    	return SingletonHolder.INSTANCE;  
    }  
}

6、枚举

  • jdk1.5起
  • 线程安全,自动支持序列化机制,非懒加载
public enum Singleton {
       
    INSTANCE;  
    public void whateverMethod() {
       
    }  
}

7、concurrent包

7.1、常见类

1、ConcurrentHashMap
2、Runnable
3、ReentrantLock
4、CyclicBarrier 循环栅栏:用于多个线程互相等待,每次调用await() 方法,计数器减一
5、CountDownLatch:用于一个线程等待多个线程,每次调用countdown() 方法,计数器减一
6、Semaphore 信号量semaphore.acquire();semaphore.release();
7、Exchanger:可以在两个线程之间交换数据,只能是2个线程,他不支持更多的线程之间互换数据。
8、ArrayBlockingQueueLinkedBlockingQueue

ArrayBlockingQueue LinkedBlockingQueue
阻塞实现方式 通知模式实现 通知模式实现
大小 必须指定初始化大小 构造器可以不初始化,默认为INTEGER.MAX_VALUE
插入取出元素是否会创建和销毁元素 不会
生产者消费者锁的使用情况 同一把锁 各自使用不同的锁

7.2、sleep() 和 wait()

  • sleep 是 Thread 类的方法,wait 是 Object 类的方法
  • sleep 没有释放锁

8、ReentrantLock的公平锁和非公平锁

1、所谓公平锁,就是线程按照执行顺序排成一排,依次获取锁

2、非公平锁和公平锁的两处不同

  • 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了
  • 非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。

9、synchronized

1、synchronized和volatile,特性上最大的区别就在于原子性,volatile不具备原子性
2、偏向锁、轻量级锁,重量级锁;只能升级,不能降级
3、锁的升级过程

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

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

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

  • 轻量级锁的升级的两种情况
    1、但是如果自旋的时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,如果自旋次数到了线程1还没有释放锁
    2、或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象

3、那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。

10、HashMap、ConcurrentHashMap

  • 1.7头插法(容易产生死循环,线程不安全), 1.8尾插法
  • 1.8扩容使用 hashCode & oldCap 判断是否为零,然后插入两个list,最后把两个list放在新的桶中。
  • JDK1.7 HashMap线程不安全体现在:死循环、数据丢失
    JDK1.8 HashMap线程不安全体现在:数据覆盖
    • 1.8数据覆盖:在线程1找到插入点准备插入的时候时间片用完了,线程2在相同的桶下标处插入后,线程1再次运行不会进行hash碰撞检测,直接插入覆盖线程2的数据
    • 1.7死循环:主要是扩容时头插法产生的。

ConcurrentHashMap

  • ConcurrentHashMap中只用写锁,没有读锁:读是使用 volatile 实现安全的
    static class Node<K,V> implements Map.Entry<K,V> {
           
    	final int hash;
    	final K key;
    	// 可以看到这些都用了volatile修饰
    	volatile V val;
    	volatile Node<K,V> next;
    }
    

二、JAVA虚拟机

1、引用类型

  • 强引用
  • 弱引用:一定会被回收
  • 软引用:内存不够的时候才回被回收
  • 虚引用:回收的时候得到一个通知

2、垃圾回收 GC

1、GC Root:

  • 虚拟机栈中局部变量表中引用的对象
  • 本地方法栈中 JNI 中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中的常量引用的对象

2、方法: 标记清除,标记整理,复制
3、垃圾回收的空间

  • 堆:新生代和老年代
    • 新生代:复制算法
    • 老年代:标记清除和标记整理
  • 方法区(元空间):

4、垃圾收集算法

  • 新生代:serial、parNew、parallel scavenge
  • 老年代:CMS、serial old、parallel old
  • G1:老年代新生代不再物理隔离,而是分成一个个小的单元交叉在内存中

四、数据库

1、快照读 (snapshot read)与当前读 (current read)

  • 快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。
  • 当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。
  • 简单的select操作,属于快照读;插入/更新/删除操作,属于当前读

2、radis为什么这么快

  • 基于内存操作
  • 单线程,避免CPU切换

五、linux

1、进程调度算法

  • CFS(Completely Fair Schedule):调用vruntime最小的进程
  • CFS的vruntime += 处理器运行时间 * nice对应的权重
  • 采用红黑树存储vruntime,调用的时候方便查找
    蚂蚁三面准备笔记_第2张图片

2、开源协议

蚂蚁三面准备笔记_第3张图片

3、开源软件

  • JDK、Spring、mybatis、MySql、Hibernate、Tomcat、eclipse、Struts
  • linux, LibreOffice、git、VSCode

4、查看端口号

lsof -i:8080
lsof -i
netstat

六、Java Spring

1、解决循环依赖问题

  • Spring实例化一个bean的时候,是分两步进行的,首先实例化目标bean,然后为其注入属性
  • 实例化之后虽然属性没有完全注入好,但是可以被引用

七、其他

1、哈希算法

  • MD5 和 SHA-1 以及 sha256

2、分布式锁

  • radis:setNX,expire设置过期时间,保证一致性
  • zookeeper: Zookeeper是一个分布式协调服务
  • mechache:

参考

本文是想到某个知识点就查,相当于一个汇总,前期很多参考没写出来
Java并发——Synchronized关键字和锁升级,详细分析偏向锁和轻量级锁的升级

你可能感兴趣的:(Java,java,面试)