一、Java 基础
二、集合框架
1、hashMap的解读
? ?hashMap 是一个以键值对形式存储的一个集合类。他在jdk1.7 和 jdk1.8 之间,他的实现策略有所不同,其中比较重要的两个区别就是 数据结构 和 头插尾插
? ?在JDK1.7的时候,hashMap 采用的数据结构是 数组加链表。但是到了JDK1.8 之后就是数组加链表加红黑树了。加入红黑树是为了提高他的查询效率。
? ?还有一点就是在JDK1.7之前,当我们遇到哈希碰撞,需要在链表上添加数据的时候,采用的是 头插法;但是到了JDK1.8的时候,改用了 尾插法。因为头插法在多线程的情况下,会导致一些问题。比如说,他会形成循环链表,因为他本身就是线程不安全的,当有多个线程线程同时走到扩容的方法时候,多个线程同时扩容,会产生一个死的 循环的 链表,循环链表的坏处就是,当我新插入一个新的节点的时候,他就会永远找不到尾结点。永远找不到尾结点就跟死循环一样,把CPU卡死,耗尽CPU性能,所以为了解决这个问题,在JDK1.8之后,改为了尾插法。
? ?接下来就以JDK1.8,聊聊 hashMap的原理。
? ?首先我们在创建hashMap的时候,根据阿里开发手册要求,让我们传入一个它的初始化容量。就是在我们预知的前提下,我们预知将来可能要插入多少条数据的情况下,我们最好能传入一个初始化容量,而且这个容量最好是一个2的次幂。
? ?当然,如果不传,这个初始化容量就是16,或者你传入了15,最后通过它底层的 tableSizeFor 方法,通过一系列的 与运算,也会得到一个距离15最近的一个2的次幂,也就是16。
? ?然后,我们在往hashMap里面添加数据的时候,就会产生两个问题。一个就是扩容的问题,还一个是树化的问题。
? ?关于扩容,在 hashMap 里面有一个成员变量,叫做加载因子,默认是 0.75。当我们插入的节点数量 >= 容量 * 加载因子。也就是默认的 >= 16 * 0.75 = 12。也就是当我们插入的数据大于等于12的时候,他就会进行一个扩容。
? ?关于树化,在源码里面也有一个成员变量,叫树化的最小容量,默认是64,也就是当这个数组容量不足64的时候,会优先选择扩容而不是树化。只有数组容量大于了 64,并且它的链表长度 >= 8,这时候才会进行树化。还有一个成员变量是 树的阈值,就是当这个树化结构 小于 6 的时候,他又会回到链表结构了。
? ? ?源码分析
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and the default load factor (0.75).
*
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative.
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* 再 new 一个 hashMap的时候可以传入一个初始化容量,
* 这个再阿里巴巴的开发手册里也是要求我们去传入这个容量的。
* 这个初始容量就是我们数组的初始默认大小(他一定是2的幂次方)
* 因为他源码里面的一个tableSizeFor这个方法,他就会通过一系列的
* 位移运算,返回一个最接近2的次幂的一个数,用这个作为数组容量
*
*
*
*
*/
// 默认的初始化容量, 1 左移 4 位 也就是 1 * 2^4 = 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 最大的容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认的加载因子,在数组扩容的时候,当数组达到某个阈值的时候,才会扩容
// 为什么是0.75?这是他们经过大量的计算得出的最佳结果,他在当初始容量
// 乘以加载因子,当大于这个数的时候,才回去扩容
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 树化的一个阈值,就是在链表达到这个阈值的时候,才会树化,生成红黑树
// 为什么是8,因为根据泊松分布原则,到了第八个节点的时候,他的树化概率就
// 会非常的低了,因为树化节点大小是普通节点的两倍,所以我们要避免树化、
static final int TREEIFY_THRESHOLD = 8;
// 树化的一个阈值,当树形结构低于6的时候,转为链表结构
static final int UNTREEIFY_THRESHOLD = 6;
// 最小的树化容量默认是64
static final int MIN_TREEIFY_CAPACITY = 64;
? ?
三、扩容的时机
1、数组的size大于等于 初始容量*加载因子 16*0.75=12;
2、扩容的大小 <<1 左移了一位 就是乘以2倍
四、树化的时机
1、数组容量大于等于 64
2、链表的长度大于等于 8
五、树化的过程
1、把普通节点Node转换为树形节点treeNode
2、调用treeify进行树化
六、头插与尾插
1、JDK1.7采用的头插法
????????JDK1.8采用的尾插法 数据结构:加入红黑树
2、头插法的优点:
????????他的查找效率会更好,它只要进行一次hash,找到它的头结点,然后让新节点next指向头结点,把这个新节点直接赋值给table就可以的。
头插法的缺点:
????????它在多线程的情况下,有可能会产生一个循环链表,因为他本身就不是线程安全的,当有多个线程同时走到扩容的方法的时候,多个线程同时扩容,会产生一个死的循环的链表,循环链表的坏处就是,当我新插入一个新的节点的时候,他就会永远找不到尾结点,永远找不到尾结点就跟死循环一样,把我们CPU直接卡死。
尾插法需要遍历,直到找到最后一个尾结点,才能插入。
??
。
三、多线程、高并发
1、实现多线程的几种方法
? ? ? ? 1、继承 Thread 类 ;
? ? ? ? 2、实现 Runnable 接口;??
? ? ? ? 3、实现 Callable 接口,它是有返回值的,可以抛异常,实现是 call() 方法
? ? ? ? 4、基于线程池 创建
2、继承 Thread 类 代码
// Lock三部曲
// 1、 new ReentrantLock(); 英 /ri??entr?nt/ 软安串得
// 2、 lock.lock(); // 加锁
// 3、 finally => lock.unlock(); // 解锁 英 /?fa?n?li/
new Thread(()->{
// 线程调用资源类,这里写你的业务代码
},"A").start();
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}
MyThread myThread1 = new MyThread();
myThread1.start();
3、实现 Runnable 接口 代码
// 如果自己的类已经 extends 另一个类,就无法直接 extends Thread,
// 此时,可以实现一个 Runnable 接口。
Runnable 接口。
public class MyThread extends OtherClass implements Runnable {
public void run() {
System.out.println("MyThread.run()");
}
}
//启动 MyThread,需要首先实例化一个 Thread,并传入自己的 MyThread 实例:
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
//事实上,当传入一个 Runnable target 参数给 Thread 后, Thread 的 run()方法就会调用
target.run()
public void run() {
if (target != null) {
target.run();
}
}
4、ExecutorService、Callable、Future 有返回值的线程 代码
// 有返回值的任务必须实现 Callable 接口,类似的,无返回值的任务必须 Runnable 接口。执行
// Callable 任务后,可以获取一个 Future 的对象,在该对象上调用 get 就可以获取到 Callable 任务
// 返回的 Object 了,再结合线程池接口 ExecutorService 就可以实现传说中有返回结果的多线程了。
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
int taskSize = 10;
//创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 创建多个有返回值的任务
List<Future> list = new ArrayList<>();
for (int i = 0; i < taskSize; i++) {
Callable callable= new MyCallable();
// 执行任务并获取 Future 对象
Future future = pool.submit(callable);
list.add(future);
}
// 关闭线程池
pool.shutdown();
// 获取所有并发任务的运行结果
for (Future future : list) {
// 从 Future 对象上获取任务的返回值,并输出到控制台
System.out.println("res = " + future.get().toString());
}
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("call()");
return 1024;
}
}
5、基于线程池的方式 代码
? ? ? ? 线程池:三大方法、7大参数、4种拒绝策略
线程池的好处:
????????1、降低资源的消耗
????????2、提高响应的速度
????????3、方便管理。
线程复用、可以控制最大并发数、管理线程
?5.1、线程池:三大方法
// 单个线程
ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 创建一个固定的线程池的大小
ExecutorService threadPool = Executors.newFixedThreadPool(5);
// 可伸缩的,遇强则强,遇弱则弱
ExecutorService threadPool = Executors.newCachedThreadPool();
// Executors 工具类、3大方法
public class Demo01 {
public static void main(String[] args) {
// 单个线程
ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 创建一个固定的线程池的大小
ExecutorService threadPool = Executors.newFixedThreadPool(5);
// 可伸缩的,遇强则强,遇弱则弱
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 100; i++) {
// 使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
5.2、线程池:七大参数
- int corePoolSize, // 1、核心线程池大小
- int maximumPoolSize, // 2、最大核心线程池大小
- long keepAliveTime, // 3、超时了没有人调用就会释放
- TimeUnit unit, // 4、超时单位
- BlockingQueue<Runnable> workQueue, // 5、阻塞队列
- ThreadFactory threadFactory, // 6、线程工厂:创建线程的,一般不用动
- RejectedExecutionHandler handle // 7、拒绝策略
源码分析:
// 创建单一线程的线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
// 创建 固定线程数的线程池
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(5,5,
0L,TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
// 创建 缓存的线程池
public static ExecutorService newCachedThreadPool(){
return new ThreadPoolExecutor(0,Integer.MAX_VALUE,
60L,TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//本质ThreadPoolExecutor()
public ThreadPoolExecutor(
int corePoolSize, // 1、核心线程池大小
int maximumPoolSize, // 2、最大核心线程池大小
long keepAliveTime, // 3、超时了没有人调用就会释放
TimeUnit unit, // 4、超时单位
BlockingQueue<Runnable> workQueue, // 5、阻塞队列
ThreadFactory threadFactory, // 6、线程工厂:创建线程的,一般不用动
RejectedExecutionHandler handle // 7、拒绝策略) {
if(corePoolSize< 0||
maximumPoolSize<=0||
maximumPoolSize<corePoolSize ||
keepAliveTime< 0)
throw new IllegalArgumentException();
if(workQueue==null||threadFactory==null||handler==null)
throw new NullPointerException();
this.acc=System.getSecurityManager()==null?
null:
AccessController.getContext();
this.corePoolSize=corePoolSize;
this.maximumPoolSize=maximumPoolSize;
this.workQueue=workQueue;
this.keepAliveTime=unit.toNanos(keepAliveTime);
this.threadFactory=threadFactory;
this.handler=handler;
}
5.3、线程池:四种拒绝策略
// 队列满了,还有进来的,不处理这个,直接抛出异常
new ThreadPoolExecutor.AbortPolicy()
// 谁调用的再回到那个线程,由调用线程处理该任务
new ThreadPoolExecutor.CallerRunsPolicy()
//队列满了,丢掉任务,不会抛出异常!
new ThreadPoolExecutor.DiscardPolicy()
//队列满了,尝试去和最早的竞争,也不会抛出异常!
new ThreadPoolExecutor.DiscardOldestPolicy()
5.4、线程池:小结与扩展
?
????????根据阿里开发手册的强制要求:线程池不予许使用 Executors 创建,而是通过 ThreadPoolExecutor 的方式,来规避资源耗尽的风险。
????????因为使用 FixedThreadPool 和 SingleThreadPool,他允许的请求队列长度为 Integer.MAX_VALUE,他的值约为 21亿。这可能会堆积大量的请求,从而导致 OOM,内存就爆了。
????????因为使用 CachedThreadPool,他允许创建的线程数量为 Integer.MAX_VALUE,值也约为 21 亿,这可能会创建大量的线程,从而导致 OOM,内存也爆掉了。
????????所以我们正常工作中用的都是 ThreadPoolExecutor,这种手动创建线程池的方式,就是写一个线程池的配置类,然后加上 @Configuration 这个注解,手写一个 ThreadPoolTaskExecutor 类。后面要用 这个类就 @Autowired一下就行。
5.5、工作中的 线程池配置类
@Configuration
public class ThreadPoolConfig
{
// 核心线程池大小
private int corePoolSize = 50;
// 最大可创建的线程数
private int maxPoolSize = 200;
// 最大线程到底该如何定义
// 1、CPU 密集型,几核,就是几,可以保持CPu的效率最高!
// 2、IO 密集型 > 判断你程序中十分耗IO的线程,
// 程序 15个大型任务 io十分占用资源!
// 获取CPU的核数
int cpu_number = Runtime.getRuntime().availableProcessors()
System.out.println(cpu_number);
// 队列最大长度
private int queueCapacity = 1000;
// 线程池维护线程所允许的空闲时间
private int keepAliveSeconds = 300;
@Bean(name = "threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor()
{
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(maxPoolSize);
executor.setCorePoolSize(corePoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveSeconds);
// 线程池对拒绝任务(无线程可用)的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//队列满了,尝试去和最早的竞争,也不会抛出异常!
//executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
return executor;
}
}
// 调用
@Autowired
ThreadPoolConfig threadPoolConfig;
@Test
void te() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = threadPoolConfig.threadPoolTaskExecutor();
for (int i = 0; i < 50; i++) {
final int j = i;
threadPoolTaskExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+" --j = "+j+" OK");
});
}
}
6、如何停止一个正在运行的线程
????????1、使用 interrupt (英?/??nt??r?pt/? 英特 rua 普特)方法来中断线程
????????2、使用 抛异常 停止线程
7、notify() 和 notifyAll() 有什么区别?
????????1、notify 随机唤醒某一个wait线程,notifyAll会唤醒全部wait的线程
????????2、所以 notify 可能会造成死锁,而notifyAll 不会
8、sleep()和 wait() 有什么区别?
????????1、sleep() 属于 Thread类中的静态方法 ;而 wait() 属于object类中的成员方法。
????????2、sleep() 是线程类 Thread 的方法,他不涉及线程通信,调用的时候会暂停这个线程的指定时间,但是监控依然保持着。所以不会释放锁,只要到时间自动恢复;而 wait()是 Object类的方法,他是用于线程间的通信,调用这个wait的方法,会放弃这个对象锁,进入等待队列。等待调用notify或者notifyAll唤醒线程,他才会进入到对象锁定池,准备获得对象锁进入运行状态。
????????3、wait、notify、notifyAll,他们只能在同步方法或者同步代码块中使用;而sleep() 可以再任何地方使用。
????????4、sleep()方法必须要捕获一个中断异常(Interrupted Exception 英?/?k?sep?n/? 一可 塞普省);而 wait()、notify、notifyAll不需要捕获异常。
? ? ? ? 5、wait() 方法必须要配合 while循环使用,不能使用 if 判断,这会导致虚假唤醒。
while (number!=0){ //0
// 等待
this.wait();
}
? ? ? ? 5.1、如果需要精准唤醒,就需要 JUC包下的Condition(译:条件 英?/k?n?d??n/ 肯第省),
他可以精准的通知和唤醒线程
// Reentrant 译:可重入 英 /ri??entr?nt/ 瑞 安穿特
private Lock lock = new ReentrantLock();
// 创建多个 condition 做唤醒、通知
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
// 在 while 循环里 condition1 等待
while (number!=1){
// 等待
condition1.await();
}
// 业务代码
// signal (译:信号通知 英 /?s?ɡn?l/ C哥老)
// 唤醒 2号 condition,做到精准通知
condition2.signal();
9、volatile 是什么?
volatile 是 java虚拟机提供的一种轻量级的同步框架:
作用:
????????1、保证可见性
????????2、不保证原子性
????????3、禁止指令重排序????????
9.1、保证可见性
????????谈到可见性就要谈到 JMM。JMM 就是Java内存模型,他本身就是一种抽象的概,实际上并不存在。他描述的就是一种规范或规则。
在 JMM 关于同步的规定:
????????1、加锁 和 解锁 都必须是同一把锁
????????2、线程加锁前,必须读取主内存的最新值,到自己的工作内存中
????????3、线程解锁前,必须把共享变量的值刷新会主内存中
四、Spring 框架
1、Spring MVC 的执行流程
?
1、 用户发送请求至前端控制器DispatcherServlet。?
2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4、 DispatcherServlet调用HandlerAdapter处理器适配器。
5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、 Controller执行完成返回ModelAndView。
7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9、 ViewReslover解析后返回具体View。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、 DispatcherServlet响应用户
自己的话描述:
?? ? ? ? 1、首先用户发起请求,到前段控制器,也就是 DispatcherServlet,它相当于一个转发器,负责接收请求,响应结果。也就是相当于 mvc 模式中的 c。
? ? ? ? 2、然后,DispatcherServlet 接收到请求,再去调用 HandlerMapping 也就是 处理器映射器。他就是根据 用户请求的 rul 找到对应的 Handler,就是处理器。并且在 SpringMVC 中,提供了 不同的映射器实现不同的映射方式。例如:通过配置文件,也就是 xml 文件 ;还有 实现接口方式 和 注解方式。
? ? ? ? 3、然后,这个 处理器映射器 根据相应的映射方式(xml 配置、或者注解等),找到这个具体的 Handler 处理器。生成 处理器对象,及处理器拦截器(如果有则生成)一并返回给?DispatcherServlet 。?
? ? ? ? 4、DispatcherServlet? 再去调用 HandlerAdapter 处理器适配器。(Adapter 英?/??d?pt?(r) :额大普特/ ),它的作用就是按照特定规则,也就是?HandlerAdapter 的要求执行 Handler。并且这个?HandlerAdapter 也用到了一个 适配器模式,他可以通过扩展适配器 对 更多类型的 处理器 进行执行。
? ? ? ? 5、然后,这个?HandlerAdapter 经过适配后,再去调用具体的 Handler 处理器,这就是我们所写的 Controller 层的代码了,前四部都是 SpringMVC帮我做好的。而我们这层 Controller 就叫 后端控制器,DispatcherServlet 叫做 前段控制器
? ? ? ? 6、然后,这个 Controller 执行完后,就开始放回一个 ModelAndView。
? ? ? ? 7、HandlerAdapter 再将 Controller 的执行结果,就是这个 ModelAndView 返回给?DispatcherServlet 。
? ? ? ? 8、DispatcherServlet 再将这个 ModelAndView 传给 ViewReslover 视图解析器。(Reslover 英[r??z?lv?] :瑞子over)。他的作用就是根据 逻辑视图名 解析成 真正的视图 View。
? ? ? ? 9、ViewReslover 解析后,返回具体的 View。
? ? ? ? 10、?DispatcherServlet 根据 View 进行 渲染视图(就是将 Model 里面的数据 填充到 View里面)
? ? ? ? 11、最后就是?DispatcherServlet ?将整个封装好的页面 响应给用户了,这就是一个完整的 SpringMVC 工作流程了。
?
?
?
?
五、MySQL 面试题
六、Redis 缓存
七、RabbitMQ 消息中间件
八、ElasticSearch 搜索引擎
九、Zookeeper + Dubbo 微服务
十、Linux 面试题
|