面试:
java中创建线程方式:
1.继承于thread:
1.创建一个继承于Thread类的子类
2.重写Thread类的run()
将此线程执行的操作声明在run()中
3.创建Thread类的子类的对象
4.通过此对象调用start()方法
public class ThreadTest02 {
public static void main(String[] args) {
//3.创建实现类的对象
MyThread02 t1 = new MyThread02();
//4.通过此对象调用start()方法
t1.start(); //启动当前线程main方法,调用Thread线程的run();
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i + "*****");
}
}//结果二
}
}
//1.创建一个继承于Thread类的子类
class MyThread02 extends Thread {
//2.重写Thread类的run()
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i + " ");
}
}//结果一
}
}
2.实现Runnable接口
1.创建了一个实现了Runnable接口的类
2.实现类去实现Runnable中的抽象方法:run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()
public class ThreadTest03 {
public static void main(String[] args) {
//3.创建实现类的对象
RunnableDemo r = new RunnableDemo();
//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(r);
t1.setName("线程一");
//5.通过Thread类的对象调用start()
t1.start();
}
}
//1.创建了一个实现了Runnable接口的类
class RunnableDemo implements Runnable {
//2.实现类RunnableDemo去实现Runnable中的抽象方法:run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
3.实现Callable接口
1.创建一个Callable接口的实现类(指明返回结果的类型),并覆写Callable接口的call方法
2.实例化Callable接口的实现类,并将其作为FutureTask类的构造方法的参数实例化FutureTask类
3.将FutureTask类的实例化对象作为Thread类中构造方法的target,实例化Thread类,同时调用start()方法启动线程
4.通过调用FutureTask类的实例化对象的get方法获取线程执行的结果
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyThread implements Callable<String> {
private String title;
public MyThread(String title) {
this.title = title;
}
public String call() {
for (int i = 0; i < 5; i++) {
System.out.println(this.title + ",i = " + i);
}
return this.title+"执行完毕";
}
public static void main(String[] args) {
Callable<String> myThread1=new MyThread("myThread1");
Callable<String> myThread2=new MyThread("myThread2");
Callable<String> myThread3=new MyThread("myThread3");
FutureTask<String> task1=new FutureTask<>(myThread1);
FutureTask<String> task2=new FutureTask<>(myThread2);
FutureTask<String> task3=new FutureTask<>(myThread3);
new Thread(task1).start();
new Thread(task2).start();
new Thread(task3).start();
try {
System.out.println(task1.get());
System.out.println(task2.get());
System.out.println(task3.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
synchronized和volitile关键字区别:
1.volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量而
2 .synchronized关键字可以修饰方法以及代码块。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用 synchronized 关键字的场景还是更多一些。
3.多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞
4.volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。
5.volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。
synchronized和reentrantLock区别:
1.ReentrantLock是可重入锁,锁定的是线程,即使不同对象加锁,只要线程没有改变,就一直持有锁;
比如有一个启动方法调用:
A.XX()
{
lock.lock();
B.XX()//假设B对象里面也使用此lock做了一次加锁和解锁;
C.XX()//假设C对象里面也使用此lock做了一次加锁和解锁;
lock.unlock();
}
–在此例子中,如果使用synchronized锁定对象B和C,则如果对象B退出synchronized区域时,此时B对象的锁
可能被其他线程获取(比如其他方法调用了关闭),则此启动线程就无法正常启动了;
2.synchronized:锁定具体对象或者类,和线程无关,属于一种简易的锁;
所以同一对象中的synchronized关键字有以下几种情况:
1)synchronized(this)和synchronized XX()非静态方法会互斥;//属于同一对象
2)synchronized(this)和synchronized (otherObj)不会互斥;//属于不同对象
3)synchronized(otherObj1)和synchronized (otherObj2)不会互斥;//属于不同对象;
4)synchronized(this)和synchronized static XX()静态方法不会互斥;//属于不同对象,一个属于this,一个属于Class对象;
hashmap在jdk1.7的实现和jdk1.8的实现方式有什么不同
HashMap是应用更广泛的哈希表实现,而且大部分情况下,都能在常数时间性能的情况下进行put和get操作。要掌握HashMap,主要从如下几点来把握:
jdk1.7中底层是由数组(也有叫做“位桶”的)+链表实现;jdk1.8中底层是由数组+链表/红黑树实现
可以存储null键和null值,线程不安全
初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂
扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入
插入元素后才判断该不该扩容,有可能无效扩容(插入后如果扩容,如果没有再次插入,就会产生无效扩容)
当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素需分配更均匀
1.7中是先扩容后插入新值的,1.8中是先插值再扩容
为什么说HashMap是线程不安全的?在接近临界点时,若此时两个或者多个线程进行put操作,都会进行resize(扩容)和reHash(为key重新计算所在位置),而reHash在并发的情况下可能会形成链表环。总结来说就是在多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。为什么在并发执行put操作会引起死循环?是因为多线程会导致HashMap的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远不为空,就会产生死循环获取Entry。jdk1.7的情况下,并发扩容时容易形成链表环,此情况在1.8时就好太多太多了。因为在1.8中当链表长度大于阈值(默认长度为8)时,链表会被改成树形(红黑树)结构。
在HashMap中,null可以作为键,这样的键只有一个,但可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示HashMap中没有该key,也可以表示该key所对应的value为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个key,应该用containsKey()方法来判断。而在Hashtable中,无论是key还是value都不能为null。
springbeen初始化过程:
Spring 启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配号Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-97tNzZMI-1638802234269)(E:\学习\java面试\1.png)]
1、ResourceLoader从存储介质中加载Spring配置信息,并使用Resource表示这个配置文件的资源;
2、BeanDefinitionReader读取Resource所指向的配置文件资源,然后解析配置文件。配置文件中每一个解析成一个BeanDefinition对象,并保存到BeanDefinitionRegistry中;
3、容器扫描BeanDefinitionRegistry中的BeanDefinition,使用Java的反射机制自动识别出Bean工厂后处理后器(实现BeanFactoryPostProcessor接口)的Bean,然后调用这些Bean工厂后处理器对BeanDefinitionRegistry中的BeanDefinition进行加工处理。主要完成以下两项工作:
1)对使用到占位符的元素标签进行解析,得到最终的配置值,这意味对一些半成品式的BeanDefinition对象进行加工处理并得到成品的BeanDefinition对象;
2)对BeanDefinitionRegistry中的BeanDefinition进行扫描,通过Java反射机制找出所有属性编辑器的Bean(实现java.beans.PropertyEditor接口的Bean),并自动将它们注册到Spring容器的属性编辑器注册表中(PropertyEditorRegistry);
4.Spring容器从BeanDefinitionRegistry中取出加工后的BeanDefinition,并调用InstantiationStrategy着手进行Bean实例化的工作;
5.在实例化Bean时,Spring容器使用BeanWrapper对Bean进行封装,BeanWrapper提供了很多以Java反射机制操作Bean的方法,它将结合该Bean的BeanDefinition以及容器中属性编辑器,完成Bean属性的设置工作;
6.利用容器中注册的Bean后处理器(实现BeanPostProcessor接口的Bean)对已经完成属性设置工作的Bean进行后续加工,直接装配出一个准备就绪的Bean。
Spring容器确实堪称一部设计精密的机器,其内部拥有众多的组件和装置。Spring的高明之处在于,它使用众多接口描绘出了所有装置的蓝图,构建好Spring的骨架,继而通过继承体系层层推演,不断丰富,最终让Spring成为有血有肉的完整的框架。所以查看Spring框架的源码时,有两条清晰可见的脉络:
1)接口层描述了容器的重要组件及组件间的协作关系;
2)继承体系逐步实现组件的各项功能。
接口层清晰地勾勒出Spring框架的高层功能,框架脉络呼之欲出。有了接口层抽象的描述后,不但Spring自己可以提供具体的实现,任何第三方组织也可以提供不同实现, 可以说Spring完善的接口层使框架的扩展性得到了很好的保证。纵向继承体系的逐步扩展,分步骤地实现框架的功能,这种实现方案保证了框架功能不会堆积在某些类的身上,造成过重的代码逻辑负载,框架的复杂度被完美地分解开了。
Spring组件按其所承担的角色可以划分为两类:
1)物料组件:Resource、BeanDefinition、PropertyEditor以及最终的Bean等,它们是加工流程中被加工、被消费的组件,就像流水线上被加工的物料;
2)加工设备组件:ResourceLoader、BeanDefinitionReader、BeanFactoryPostProcessor、InstantiationStrategy以及BeanWrapper等组件像是流水线上不同环节的加工设备,对物料组件进行加工处理。
spring循环依赖如何解决:
Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!
mybaties框架:
mybatis是一个持久层的框架。所谓的持久层,就是我们三层中的dao层。主要负责跟数据库进行交互。可以建立数据库表和系统中的对象的一对一映射关系。这种框架我们称之为orm框架。但是mybatis框架需要自己写sql语句,且不能像hibernate那样自动生成sql语句,并且建立实体类和数据库的映射。所以我们说它是不完全的orm框架。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fQs4HOm4-1638802234271)(E:\学习\java面试\image-20211206224318197.png)]
MyBatis的xml配置文件包含了设置和影响MyBatis行为的属性,XML配置文件的层次饥结构如下:
Configuration
Properties属性
Settings设置
typeAliases类型别名
typeHandlers类型处理器
ObjectFactory对象工厂
Plugins插件
Environments环境
Environment环境变量
transactionManager事物管理器
datasource数据源
mappers映射器
redis持久化:
Redis对数据的操作都是基于内存的,当遇到了进程退出、服务器宕机等意外情况,如果没有持久化机制,那么Redis中的数据将会丢失无法恢复。有了持久化机制,Redis在下次重启时可以利用之前持久化的文件进行数据恢复。理解和掌握Redis的持久机制,对于Redis的日常开发和运维都有很大帮助,也是在大厂面试经常被问到的知识点。Redis支持的两种持久化机制:
RDB:把当前数据生成快照保存在硬盘上。
AOF:记录每次对数据的操作到硬盘上。
RDB持久化
RDB(Redis DataBase)持久化是把当前Redis中全部数据生成快照保存在硬盘上。RDB持久化可以手动触发,也可以自动触发。
手动触发
save和bgsave命令都可以手动触发RDB持久化。
save命令
执行save命令会手动触发RDB持久化,但是save命令会阻塞Redis服务,直到RDB持久化完成。当Redis服务储存大量数据时,会造成较长时间的阻塞,不建议使用。
AOF(Append Only File)持久化是把每次写命令追加写入日志中,当需要恢复数据时重新执行AOF文件中的命令就可以了。AOF解决了数据持久化的实时性,也是目前主流的Redis持久化方式。
spa与mpa区别:
1、单页Web应用(single page web application,SPA),就是只有一张Web页面的应用,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。
页面跳转: js渲染
优点: 页面切换快
缺点: 首屏时间稍慢,SEO差
2、每一次页面跳转的时候,后台服务器都会返回一个新的html文档,这种类型的网站也就是多页网站,也叫多页应用(MPA)。
页面跳转: 返回HTML
优点: 首屏时间快,SEO效果好
缺点: 页面切换慢