一:Java基础
1.public final native Class<?>getClass()//native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写。
public native int hashCode()//native方法,用于返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。
public boolean equals(Object obj)//用于比较2个对象的内存地址是否相等,String类对该方法进行了重写用户比较字符串的值是否相等。
protected native Object clone()throws CloneNotSupportedException//naitive方法,用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象x,表达式x.clone()!=x为true,x.clone().getClass()==x.getClass()为true。Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。
public String toString()//返回类的名字 实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法。
public final native void notify()//native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
public final native void notifyAll()//native方法,并且不能重写。跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
public final native void wait(long timeout)throws InterruptedException//native方法,并且不能重写。暂停线程的执行。注意:sleep方法没有释放锁,而wait方法释放了锁。timeout是等待时间。
public final void wait(long timeout,int nanos)throws InterruptedException//多了nanos参数,这个参数表示额外时间(以毫微秒为单位,范围是0-999999)。所以超时的时间还需要加上nanos毫秒。
public final void wait()throws InterruptedException//跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
protected void finalize()throws Throwable{}//实例被垃圾回收器回收的时候触发的操作
2.内存泄露memory leak,是指程序在申请内存后,无法释放已申请的内存空间。内存溢出out of memory,是指程序在申请内存时,没有足够的内存空间供其使用。memory leak会最终会导致out of memory!
3.红黑树的五个特性
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。[注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
4. Transactional事务不要滥用。事务会影响数据库的QPS,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。
5.给JVM设置-XX:+HeapDumpOnOutOfMemoryError参数,让JVM碰到OOM场景时输出dump信息。
6.链表的数据结构
单向链表:只有一个指向下一个节点的指针。
双向链表:有两个指针,一个指向前一个节点,一个后一个节点。
链表和数组的区别:数组查找快(基于下标地址的查找),插入慢(需要移动后面的元素地址).链表是插入快(只需要修改后继节点即可),查找慢(需要从头开始遍历)
链表的实际应用:LRU缓存淘汰算法,redis list的数据结构就是双向链表
7.为什么要用红黑树?
简单来说红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。
二:数据库
1.数据库包含四个隔离级别:
读未提交,可重复读,不可重复读,串行化
脏读:脏读又称无效数据读出。一个事务读取另外一个事务还没有提交的数据叫脏读。
幻读:幻读是指同样的一条查询语句,前后两次读取,数据的数量发生了改变。
不可重复读:不可重复读是指的同一批数据,前后两次读取,这一批数据的值发生了变化。
2.加索引的情景:where或者连接查询字段
三:网络
1.三次握手可以理解为吃饭。A:今晚吃啥?B:我要吃XXXX
A:知道了。这样才确保信息的可靠性
四:操作系统
1.linux中top指令中load average表示1,5,15分钟到现在的平均系统负载
五:框架知识
1.springboot读取配置文件的方式:
controller(控制层)、 service(服务层)、 repository(数据层)、 component(实体类)注解的类,都会把这些类纳入进spring容器中进行管理
Springboot默认是扫描启动类当前包下的所有类别
第一种方式是: Component+ ConfigurationProperties(prefix="library")
代码如下:
实体类:
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.util.List;
Component
ConfigurationProperties(prefix="library")
Setter
Getter
ToString
class LibraryProperties{
private String location;
private List<Book>books;
Setter
Getter
ToString
static class Book{
String name;
String description;
}
}
启动类:
package cn.javaguide.readconfigproperties;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* author shuang.kou
*/
SpringBootApplication
public class ReadConfigPropertiesApplication implements InitializingBean{
private final LibraryProperties library;
public ReadConfigPropertiesApplication(LibraryProperties library){
this.library=library;
}
public static void main(String[]args){
SpringApplication.run(ReadConfigPropertiesApplication.class,args);
}
Override
public void afterPropertiesSet(){
System.out.println(library.getLocation());
System.out.println(library.getBooks());}
}
输出:
湖北武汉加油中国加油
[LibraryProperties.Book(name=天才基本法,description........]
第二种方式是: component+ PropertySource读取指定properties文件
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
Component
PropertySource("classpath:website.properties")
Getter
Setter
class WebSite{
Value("${url}")
private String url;
}
Autowired
private WebSite webSite;
System.out.println(webSite.getUrl());//https://javaguide.cn/
2.springboot捕获处理异常的三种方式
使用 ControllerAdvice和 ExceptionHandler处理全局异常
ExceptionHandler处理Controller级别的异常
ResponseStatusException
3.可以看出大概可以把 SpringBootApplication看作是 Configuration、 EnableAutoConfiguration、 ComponentScan注解的集合。根据SpringBoot官网,这三个注解的作用分别是:
• EnableAutoConfiguration:启用SpringBoot的自动配置机制
• ComponentScan:扫描被 Component( Service, Controller)注解的bean,注解默认会扫描该类所在的包下所有的类。
• Configuration:允许在上下文中注册额外的bean或导入其他配置类
4.Dubbo的核心组件?
六:并发编程
1.多个Condition的强大之处,假设缓存队列中已经存满,那么阻塞的肯定是写线程,唤醒的肯定是取线程,假设只有一个Condition会有什么效果呢,缓存队列中已经存满,这个Lock不知道唤醒的是取线程还是写线程了,如果唤醒的是取线程,皆大欢喜,如果唤醒的是写线程,那么线程刚被唤醒,又被阻塞了,这时又去唤醒,这样就浪费了很多时间。
2.Java如何解决死锁
(1)调整申请锁的范围
(2)调整申请锁的顺序
总是以相同的顺序来申请锁,比如总是先申请id大的账户上的锁,然后再申请id小的账户上的锁,这样就无法形成导致死锁的那个闭环。
3.CAS原理:CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。
CAS引发出两个问题:
(1)ABA问题。解决方式是版本号控制思想,利用时间戳来判断。具体实现为使用JDK1.5原子并发包Atomic下的AtomicStampedReference。
compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp)。
第一个参数expectedReference:表示预期值。
第二个参数newReference:表示要更新的值。
第三个参数expectedStamp:表示预期的时间戳。
第四个参数newStamp:表示要更新的时间戳。
(2)性能自旋问题。解决方法为:设定自旋的时间
4.如果在时间片结束时进程还在运行,则暂停这个进程的运行,并且CPU分配给另一个进程(这个过程叫做上下文切换)
5.进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位
6.启动线程不可多次调用start()方法。Java的线程是不允许启动两次的,第二次调用必然会抛出IllegalThreadStateException,这是一种运行时异常,多次调用start被认为是编程错误。
7.Callable一般是配合线程池工具ExecutorService来使用的,ExecutorService可以使用submit方法来让一个Callable接口执行。它会返回一个Future,我们后续的程序可以通过这个Future的get方法得到结果。
上代码:
//自定义Callable
class Task implements Callable<Integer>{
Override
public Integer call()throws Exception{
//模拟计算需要一秒
Thread.sleep(1000);
return 2;
}
public static void main(String args[]){
//使用
ExecutorService executor=Executors.newCachedThreadPool();
Task task=new Task();
Future<Integer>result=executor.submit(task);
//注意调用get方法会阻塞当前线程,直到得到结果。
//所以实际编码中建议使用可以设置超时时间的重载get方法。
System.out.println(result.get());
}
}
输出结果:2
8.ThreadLocal使用场景为用来解决数据库连接、Session管理等
9.ThreadPoolExecutor提供的4种构造方法,里面的参数分别如下:
int corePoolSize:该线程池中核心线程数最大值
int maximumPoolSize:该线程池中线程总数最大值。
long keepAliveTime:非核心线程闲置超时时长。
TimeUnit unit:keepAliveTime的单位。
BlockingQueue workQueue:阻塞队列,维护着等待执行的Runnable任务对象。
RejectedExecutionHandler handler:拒绝处理策略
10.CountDownLatch案例
我们知道,玩游戏的时候,在游戏真正开始之前,一般会等待一些前置任务完成,比如“加载地图数据”,“加载人物模型”,“加载背景音乐”等等。只有当所有的东西都加载完成后,玩家才能真正进入游戏。下面我们就来模拟一下这个demo。
public class CountDownLatchDemo{
//定义前置任务线程
static class PreTaskThread implements Runnable{
private String task;
private CountDownLatch countDownLatch;
public PreTaskThread(String task,CountDownLatch countDownLatch){
this.task=task;
this.countDownLatch=countDownLatch;
}
Override
public void run(){
try{
Random random=new Random();
Thread.sleep(random.nextInt(1000));
System.out.println(task+"-任务完成");
countDownLatch.countDown();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[]args){
//假设有三个模块需要加载
CountDownLatch countDownLatch=new CountDownLatch(3);
//主任务
new Thread(()->{
try{
System.out.println("等待数据加载...");
System.out.println(String.format("还有%d个前置任务",countDownLatch.getCount()));
countDownLatch.await();
System.out.println("数据加载完成,正式开始游戏!");
}catch(InterruptedException e){
e.printStackTrace();
}
}).start();
//前置任务
new Thread(new PreTaskThread("加载地图数据",countDownLatch)).start();
new Thread(new PreTaskThread("加载人物模型",countDownLatch)).start();
new Thread(new PreTaskThread("加载背景音乐",countDownLatch)).start();
}
}
输出:
等待数据加载...
还有3个前置任务
加载人物模型-任务完成
加载背景音乐-任务完成
加载地图数据-任务完成
数据加载完成,正式开始游戏!
11.锁升级:锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态(采用自旋锁)、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
偏向锁的概念:大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁。当一个线程访问加了同步锁的代码块时,会在对象头中存储当前线程的ID,后续这个线程进入和退出这段加了同步锁的代码块时,不需要再次加锁和释放锁。而是直接比较对象头里面是否存储了指向当前线程的偏向锁。如果相等表示偏向锁是偏向于当前线程的,就不需要再尝试获得锁了
12.synchronized和ReentrantLock的区别
①两者都是可重入锁
②synchronized依赖于JVM而ReentrantLock依赖于API
③ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁
④线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。在使用notify()/notifyAll()方法进行通知时,被通知的线程是由JVM选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”
七:缓存
1.redis scan指令:用来替代Keys*的缺陷,SCAN命令是一个基于游标的迭代器(cursor based iterator):SCAN命令每次被调用之后,都会向用户返回一个新的游标,用户在下次迭代时需要使用这个新游标作为SCAN命令的游标参数,以此来延续之前的迭代过程。
SCAN命令的回复是一个包含两个元素的数组,第一个数组元素是用于进行下一次迭代的新游标,而第二个数组元素则是一个数组,这个数组中包含了所有被迭代的元素。
2.redis五种数据结构
string:key-value形式。一般用作库存计数
list:双向链表,实现分页查询
set:可以基于set轻易实现交集、并集、差集的操作
sorted set:直播排行
hash:存储用户信息
aof和rdb的区别:
aof:持续的用日志记录写操作,crash后利用日志恢复.牺牲一些性能,换取更高的缓存一致性(可以采用aof重写来释放内存)
rdb:写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份
八:消息队列
1.消息队列保证幂等性:可以用数据库为唯一主键来确保消息是否被消费过
2.消息队列保证可靠性分三个:
3.生产者的可靠性:从生产者弄丢数据这个角度来看,RabbitMQ提供transaction和confirm模式来确保生产者不丢消息;
transaction机制就是说:发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事务就会回滚(channel.txRollback()),如果发送成功则提交事务(channel.txCommit())。然而,这种方式有个缺点:吞吐量下降;
confirm模式用的居多:一旦channel进入confirm模式,所有在该信道上发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后;
rabbitMQ就会发送一个ACK给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了;
如果rabbitMQ没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作。
消费者可靠性:消费者丢数据一般是因为采用了自动确认消息模式,改为手动确认消息即可!
消费者在收到消息之后,处理消息之前,会自动回复RabbitMQ已收到消息;
如果这时处理消息失败,就会丢失该消息;
解决方案:处理消息成功后,手动回复确认消息。
消息队列可靠性:消息持久化。
处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。
这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。
这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。
那么如何持久化呢?
这里顺便说一下吧,其实也很容易,就下面两步
1.将queue的持久化标识durable设置为true,则代表是一个持久的队列
2.发送消息的时候将deliveryMode=2
这样设置以后,即使rabbitMQ挂了,重启后也能恢复数据
4.消息队列保证顺序性:
对消息进行编号,消费者处理消息是根据编号处理消息;
5.常用的交换器主要分为一下三种:
direct:如果路由键完全匹配,消息就被投递到相应的队列
fanout:如果交换器收到消息,将会广播到所有绑定的队列上
topic:可以使来自不同源头的消息能够到达同一个队列。使用topic交换器时,可以使用通配符,比如:“*”匹配特定位置的任意文本,“.”把路由键分为了几部分,“#”匹配所有规则等。特别注意:发往topic交换器的消息不能随意的设置选择键(routing_key),必须是由"."隔开的一系列的标识符组成。
6.死信消息:
(1)消息被拒绝(Basic.Reject或Basic.Nack)并且设置requeue参数的值为false
(2)消息过期了
(3)队列达到最大的长度
在rabbitmq中存在2种方可设置消息的过期时间,第一种通过对队列进行设置,这种设置后,该队列中所有的消息都存在相同的过期时间,第二种通过对消息本身进行设置,那么每条消息的过期时间都不一样
队列设置:在队列申明的时候使用x-message-ttl参数,单位为毫秒
单个消息设置:是设置消息属性的expiration参数的值,单位为毫秒
九:JVM