Ending
Tip:由于文章篇幅有限制,下面还有20个关于MySQL的问题,我都复盘整理成一份pdf文档了,后面的内容我就把剩下的问题的目录展示给大家看一下
如果觉得有帮助不妨【转发+点赞+关注】支持我,后续会为大家带来更多的技术类文章以及学习类文章!(阿里对MySQL底层实现以及索引实现问的很多)
吃透后这份pdf,你同样可以跟面试官侃侃而谈MySQL。其实像阿里p7岗位的需求也没那么难(但也不简单),扎实的Java基础+无短板知识面+对某几个开源技术有深度学习+阅读过源码+算法刷题,这一套下来p7岗差不多没什么问题,还是希望大家都能拿到高薪offer吧。
多编程的⽬的就是为了能提⾼程序的执⾏效率、提⾼程序运⾏速度,然而多线程的缺点也有:线程的滥用会给系统带来上下文切换的额外负担,并且线程间的共享变量可能造成死锁的出现。
1 什么时候需要用到多线程
我认为当程序需要提高运行速度,同时改善程序结构时需要利用到多线程。
2 什么是线程安全
线程安全指的是当一个线程在操作一个方法或者语句时,其它线程不能对其进行操作,只能等到该线程结束后才可以进行访问。
3 能想到某个业务场景来需要线程安全吗?
一:比如生产者和消费者模式中,产品的数量就需要保证线程安全,否则当多个线程进行操作时,就可能导致脏读的发生,因为多个线程可能读到的是相同的值,从而导致产品总量超出。
-
一个线程在创建之后就会处于
NEW(新建)
状态,此时调用start()
方法后线程会进入到RUNNABLE(运行)
状态; -
当线程执行 wait() 方法后会进入到
WITTING(等待)
状态,进入等待状态的线程需要依靠其它线程的通知才能够返回到运行状态; -
当使用
sleep(long time)
或者wait(long time)
方法可以将线程置于TIME_WAITING(超时等待)
状态,等时间一结束就会自动进入到运行状态; -
当线程调用同步方法时,如果该线程没有获得锁的话,该线程就会进入到
BLOCK(阻塞)
状态; -
一个线程结束之后就会进入到
TERMINATED(终止)
状态;
死锁是指两个或多个线程互相持有对方所需要的资源,并请求锁定对方的资源,导致这些线程一直处于等待其它线程释放资源的状态。
死锁的必要条件:
-
互斥条件:某个资源在任意⼀个时刻只由⼀个线程占用;
-
请求与保持条件:⼀个线程因请求资源⽽阻塞时,对已获得的资源保持不放;
-
不剥夺条件:线程已获得的资源在末使⽤完之前不能被其他线程强⾏剥夺,只有自己使⽤完毕后才释放资源;
-
循环等待条件:若干线程之间形成⼀种头尾相接的循环等待资源关系。
如何避免死锁:
我上⾯说了产⽣死锁的四个必要条件,为了避免死锁,我们只要破坏产⽣死锁的四个条件中的其中⼀个就可以了。现在我们来挨个分析⼀下:
-
破坏互斥条件 :这个条件我们没有办法破坏,因为我们⽤锁本来就是想让他们互斥的;
-
破坏请求与保持条件 :⼀次性申请所有的资源;
-
破坏不剥夺条件 :占⽤部分资源的线程进⼀步申请其他资源时,如果申请不到,可以主动释放它占有的资源;
-
破坏循环等待条件:通过按照顺序来申请资源,按某⼀顺序申请资源,释放资源则反序释放。
===========================================================================
创建多线程的方式有四种,分别是:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口、使用线程池。
四种方式的差异如下:
| 创建多线程的方式 | 特征 |
| — | — |
| 继承Thread类 | 每次使用时,只需要实现继承了 Thread 类的子类就行:HelloThread h1 = new HelloThread();
|
| 实现Runnable接口 | 使用时首先创建实现了接口的类的对象,然后将此对象作为参数传递到 Thread 类的构造器中,从而创建Thread类的对象 Window1 w = new Window1(); Thread t1 = new Thread(w);
。相比于继承Thread类,1)实现的方式没有类的单继承性的局限性;2)降低了线程对象与线程任务之间的耦合性,增强了程序的可扩展性。 |
| 实现Callable接口 | 相比于实现 Runnable 接口,1)call()可以有返回值的;2)call()可以抛出异常,被外面的操作捕获,获取异常的信息。而之前的方法只能是 try catch 捕获异常;3)Callable 是支持泛型的。 |
| 使用线程池 | 用从学校到西湖举例,前三种方法创建多线程就是每次去都要造一辆自行车,骑到西湖后就把自行车销毁,而线程池的方法就是搭乘公共交通去西湖。因此,这一种是最常用的方法,好处是:1)提高响应速度(减少了创建新线程的时间);2)降低资源消耗(重复利用线程池中线程,不需要每次都创建);3)便于线程管理。 |
可以。所以,如果任务不需要返回结果或抛出异常推荐使用 Runnable 接⼝,这样代码看起来会更加简洁。
参考文献:传送门
| | sleep() | wait() |
| :-: | :-: | :-: |
| 来源 | 来自 Thread 类 | 来自 Object 类 |
| 对锁的影响 | 没有释放锁 | 释放锁 |
| 使用范围 | 任何地方 | 只能在同步控制方法或者同步控制块里面使用,否则会抛 IllegalMonitorStateException |
| 恢复方式 | 时间到了之后线程会⾃动苏醒 | 需要其他线程调用同一对象的 notify()/nofityAll() 才能重新恢复 |
1 sleep() 和 wait() 的区别
-
sleep() 是 Tread 类的方法,而 wait() 是 Object 类的方法;
-
sleep() 不会释放锁,而 wait() 会释放锁;
-
sleep() 在任何方法里都可以使用,而 wait() / notify() 只能在同步方法中使用。
线程执行 sleep() 方法后进入超时等待状态,而执行 yield() 方法后进入就绪状态。
sleep() 方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程运行的机会;yield() 方法只会给相同优先级或更高优先级的线程以运行的机会。
线程中的 join() 用来等待指定线程终止,然后继续执行当前线程。
比如线程 A 执行了 threadB.join() 之后,其含义就是线程 A 等待 threadB 线程终止之后才从 threadB.join() 继续往下执行自己的代码。
4.5 Thread 调用 start() 方法和调用 run() 方法的区别
-
run() :普通的方法调用,在主线程中执行,不会新建一个线程来执行;
-
start():新启动一个线程,这时此线程处于就绪(可运行)状态,一旦得到 CPU 时间片,就开始执行 run() 方法。
总结: 调⽤ start() ⽅法⽅可启动线程并使线程进⼊就绪状态,直接执⾏ run() ⽅法的话不会以多线程的⽅式执⾏。
参考 Java 基础部分对其的讲解;
构造⽅法不能使⽤ synchronized 关键字修饰。构造⽅法本身就属于线程安全的,不存在同步的构造⽅法⼀说。
-
Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现;
-
Lock 在发生异常时,如果没有主动通过 unLock() 去释放锁,很可能会造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;synchronized 在发生异常时,会自动释放锁,因此不会导致死锁现象发生;
-
Lock 的使用更加灵活,可以有响应中断、有超时时间等;而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,直到获取到锁;
-
在性能上,随着近些年 synchronized 的不断优化,Lock 和 synchronized 在性能上已经没有很明显的差距了,所以性能不应该成为我们选择两者的主要原因。官方推荐尽量使用 synchronized,除非 synchronized 无法满足需求时,则可以使用 Lock。
1 synchronized 和 ReentrantLock 的异同
-
ReentrantLock 是 Lock 接口的一个实现类,而 synchronized 是 Java 中的关键字;
-
ReentrantLock 在发生异常时,需要手动声明释放锁,所以最好在finally中声明释放锁,而synchronized 在发生异常时,会自动释放锁,因此不会导致死锁现象发生;
-
ReentrantLock 更加灵活,提供的方法也更多。
4.8 synchronized 关键字和 volatile 关键字的区别
synchronized 关键字和 volatile 关键字是两个互补的存在,⽽不是对⽴的存在。
-
volatile 关键字是线程同步的轻量级实现,所以 volatile 性能肯定⽐ synchronized 关键字要好;
-
volatile 关键字只能⽤于变量,⽽ synchronized 关键字还可以修饰⽅法以及代码块;
-
volatile 关键字能保证数据的可⻅性,但不能保证数据的原⼦性。 synchronized 关键字两者都能保证;
-
volatile 关键字主要⽤于保证变量在多个线程之间的可⻅性,⽽ synchronized 关键字解决的是多个线程之间访问资源的同步性。
CPU 缓存则是为了解决 CPU 处理速度和内存处理速度不对等的问题,
缓存是内存中少部分数据的复制品。
========================================================================
池化技术的思想主要是为了减少每次获取资源的消耗,提⾼对资源的利⽤率。
使⽤线程池的好处:
-
降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗;
-
提高响应速度:当任务到达时,任务可以不需要等到线程创建就能⽴即执⾏;
-
提高线程的可管理性:线程是稀缺资源,如果⽆限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使⽤线程池可以进⾏统⼀的分配,调优和监控。
/**
-
创建线程的方式四:使用线程池
-
好处:
-
1.提高响应速度(减少了创建新线程的时间)
-
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
-
3.便于线程管理
-
corePoolSize:核心池的大小
-
maximumPoolSize:最大线程数
-
keepAliveTime:线程没有任务时最多保持多长时间后会终止
*/
public class Pool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
//3.关闭连接池
service.shutdown();
}
}
class NumberThread implements Runnable{
@Override
public void run() {
// 逻辑代码
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
// 逻辑代码
}
}
-
hreadFactory(线程工厂):用于创建工作线程的工厂;
-
corePoolSize(核心线程数):当线程池运行的线程少于corePoolSize 时,将创建一个新线程来处理请求,即使其他工作线程处于空闲状态;
-
workQueue(队列):用于保留任务并移交给工作线程的阻塞队列;
-
maximumPoolSize(最大线程数):线程池允许开启的最大线程数;
-
handler(拒绝策略):往线程池添加任务时,将在下面两种情况触发拒绝策略:1)线程池运行状态不是 RUNNING;2)线程池已经达到最大线程数,并且阻塞队列已满时;
-
keepAliveTime(保持存活时间):如果线程池当前线程数超过 corePoolSize,则多余的线程空闲时间超过 keepAliveTime 时会被终止。
-
AbortPolicy:中止策略。默认的拒绝策略,直接抛出 RejectedExecutionException。调用者可以捕获这个异常,然后根据需求编写自己的处理代码。
-
DiscardPolicy:抛弃策略。什么都不做,直接抛弃被拒绝的任务。
-
DiscardOldestPolicy:抛弃最老策略。抛弃阻塞队列中最老的任务,相当于就是队列中下一个将要被执行的任务,然后重新提交被拒绝的任务。如果阻塞队列是一个优先队列,那么“抛弃最旧的”策略将导致抛弃优先级最高的任务,因此最好不要将该策略和优先级队列放在一起使用。
-
CallerRunsPolicy:调用者运行策略。在调用者线程中执行该任务。该策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将任务回退到调用者(调用线程池执行任务的主线程),由于执行任务需要一定时间,因此主线程至少在一段时间内不能提交任务,从而使得线程池有时间来处理完正在执行的任务。
1 线程池是什么
线程池是指在初始化应用程序的过程中创建的一个线程集合,之后每次执行新的任务的时候重用这些线程而非新建一个线程。作用的话就是提高资源的利用率、提高线程的可管理性。
2 线程池的过程
-
提交任务,判断核心线程数是否达到最大;
-
如果未达到最大,则执行任务;如果达到最大,则判断任务队列是否已满;
-
如果任务队列未满,则将任务添加到任务队列中等待线程执行;如果任务队列已满,则判断线程数是否达到最大线程数;
-
如果未达到最大线程数,则创建非核心线程执行任务;反之,则执行饱和策略。
3 线程池的构造方法有哪些参数
-
corePoolSize:核心线程数
-
maximumPoolSize:最大线程数
-
keepAliveTime:当核心线程已满时,闲置线程最长可以存活的时间
-
保存任务的队列;
-
饱和策略。
4 线程池的饱和策略
-
终止策略,默认。抛出异常,让调用者捕获异常来处理;
-
抛弃策略,直接抛弃被拒绝的任务;
-
抛弃最老策略:抛弃下一个将被执行的任务;
-
调用者运行策略。
5 线程池的种类
-
newCachedThreadPool:可缓存线程池;
-
newFixedThreadPool:可指定工作线程数量的线程池;
-
newScheduleThreadPool:定长的线程池;
=============================================================================
**生产者和消费者问题是线程模型中的经典问题:**生产者和消费者在同一时间段内共用同一个存储空间,生产者往存储空间中添加产品,消费者从存储空间中取走产品,当存储空间为空时,消费者阻塞,当存储空间满时,生产者阻塞。该储存空间可以当做是一个缓冲区。
关于生产者和消费者的参考文献:传送门
import java.lang.*;
/**
- 生产者和消费者,wait()和notify()的实现
*/
最后
即使是面试跳槽,那也是一个学习的过程。只有全面的复习,才能让我们更好的充实自己,武装自己,为自己的面试之路不再坎坷!今天就给大家分享一个Github上全面的Java面试题大全,就是这份面试大全助我拿下大厂Offer,月薪提至30K!
我也是第一时间分享出来给大家,希望可以帮助大家都能去往自己心仪的大厂!为金三银四做准备!
一共有20个知识点专题,分别是:
Dubbo面试专题
JVM面试专题
Java并发面试专题
Kafka面试专题
MongDB面试专题
MyBatis面试专题
MySQL面试专题
Netty面试专题
RabbitMQ面试专题
Redis面试专题
Spring Cloud面试专题
SpringBoot面试专题
zookeeper面试专题
常见面试算法题汇总专题
计算机网络基础专题
设计模式专题
g-LE1dyCtV-1714915330027)]
Redis面试专题
[外链图片转存中…(img-rDdbdHDc-1714915330027)]
Spring Cloud面试专题
[外链图片转存中…(img-lrOWpd12-1714915330028)]
SpringBoot面试专题
[外链图片转存中…(img-4SAJfkbl-1714915330028)]
zookeeper面试专题
[外链图片转存中…(img-r6VfZivI-1714915330028)]
常见面试算法题汇总专题
[外链图片转存中…(img-VVhDtIgN-1714915330029)]
计算机网络基础专题
[外链图片转存中…(img-0w8AXhJB-1714915330029)]
设计模式专题
[外链图片转存中…(img-aqCvfCO2-1714915330029)]