JAVA面试题

1、请你说说多线程?

定义:线程是操作系统调度的最小单元,一个进程中有多个线程,它可以让一个进程并发的处理多个任务
进程内的线程共享进程中资源,因为是共享资源,处理器可以在这些线程中快速切换
操作系统–>可同时执行多个任务–>每个任务就相当于一个进程–>一个进程可以同时执行多个任务–>每个任务相当于一个线程
使用多线程的优点:
(1)减少程序响应时间
(2)提高CPU利用率
(3)创建和切换的开销小
(4)数据共享效率高
(5)简化程序结构

2、说说怎么保证线程安全?

三种保证线程安全的方式:原子类、volatile[ˈvɑːlətl]、锁
1、原子类:原子类是atomic[əˈtɑːmɪk]包下的一系列类,通过CAS比较和交换的机制实现线程安全的更新共享变量。Atomic系列也可看作乐观锁,即当操作某一段数据的时候,线程之间不会相互影响,采用非阻塞的模式,直到更新数据的时候才会进行版本的判断是否值已经进行了修改,即CAS操作。

CAS的实现原理
假设右边有很多线程,都想把左边的这个值做 +1的操作,右边的这些线程都可以看到左边这个参数的数值。所以这些线程会把看到的值拿来做计算。计算出运算之后的值应该是多少,以线程1为例,看到的值为0,要修改为1,其他线程同样如此。
这些线程都要做CAS操作,即compare and swap,比较和交换。将0拿去比较,如果左边的参数还是0的话,就换成1。很显然,右边线程都是拿0去比较的话,只有一个线程能成功。假设线程1成功交换,左边的参数就被换成1了。此时,其他线程过来做CAS操作时发现,参数已经不再是0了。所有其他线程需要做自旋操作。也就是把新看到的值拿来做计算,然后再次进行CAS操作,如果不成功就一直自旋到成功为止。

2、 volatile:volatile关键字是轻量级同步机制;volatile三大特性:可见性、不保证原子性、有序性
a. 可见性:所有变量都存放在内存中,所有线程都能对其访问,但是线程对变量的所有操作都必须在自己的工作内存中完成,线程需要先把变量拷贝到自己的工作内存中,再对它进行操作,最后将操作后的变量刷新回主内存,如果共享变量之间不可见的话,那线程就互相不知道对方的操作进度,volatile关键字实现了线程之间的可见性,线程对变量操作后立即刷新回主内存,其他线程对此变量操作需要重新读取
b. 不保证原子性:可以通过加上synchronized关键字解决此问题
c. 有序性:防止指令重排:我们写的java代码,为了提高性能,在编译器和处理器中往往会进行指令重排,例如我写的某一行代码在23行,当经过编译后这行代码在150行

3、锁:java中常见的加锁方式有两种:synchronized关键字和Lock接口
synchronized是比较早期的API,在设计之初没有考虑到超时机制、非阻塞形式,以及多个条件变量。若想通过升级的方式让它支持这些相对复杂的功能,则需要大改它的语法结构,不利于兼容旧代码。因此,JDK的开发团队在1.5新增了Lock接口,并通过Lock支持了上述的功能,即:支持响应中断、支持超时机制、支持以非阻塞的方式获取锁、支持多个条件变量(阻塞队列)。

3、说说死锁发生的条件?

争夺共享资源、相互等待、互斥条件、请求和保持条件、不剥夺条件
1、死锁定义:两个或两个以上的线程在执行过程中,因争夺共享资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

2、产生死锁的必备条件:互斥条件:某资源在一段时间内只能由一个线程占用;请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它线程占用,此时请求进程阻塞,但又对自己获得的资源保持不放;不剥夺条件:指进程已获得的资源在未使用玩之前,不能被剥夺,只能在使用完时由自己释放;- 环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合 {P0,P1,P2,···,Pn} 中的 P0 正在等待一个 P1 占用的资源;P1 正在等待 P2 占用的资源,……,Pn 正在等待已被 P0 占用的资源。

4、说说你对MVC的理解?

model:相当于entity层,存放我们的实体类,与数据库的属性值基本保持一致
view:视图层,对应用户界面
controller:控制层,controller层通过接收前端传过来的参数进行业务操作
dao:对数据库进行数据持久化操作,与mapper层相同
1、model代表的是数据,view代表的是用户界面、controller代表的是数据的处理逻辑
2、将软件分层的好处是降低对象之间的耦合度,便于代码维护

5、请你讲一下单例模式、请你手写一下单例模式?

1、单例模式:一个类有且仅有一个实例,并且自行实例化提供给整个系统使用
2、 单例模式的主要写法有两种:懒汉式饿汉式
懒汉式单例

/**
 * @Description 懒汉式单例类,调用时实例化
 * @Date 2023/2/16 9:44
 * @Version 1.0
 */
public class Singleton {

    private Singleton() {}

    private static Singleton singleton = null;

    //静态工厂方法
    private static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

解决懒汉式的线程安全问题:
a、在getInstance方法上加同步

private static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }

b、双重检查锁定

 private static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

c、静态内部类

//静态内部类实例化唯一对象
    private static class Internal {
        private static final Singleton SINGLE = new Singleton();
    }

    //静态工厂方法
    private static Singleton getInstance() {
        return Internal.SINGLE;
    }

饿汉式单例
天生就是线程安全的:在单例类被加载时,就会实例化一个对象并交给自己的引用,也就是说在线程访问单例对象之前就已经创建好了,而一个类在整个生命周期中只会加载一次,因此该单例类只会创建一个实例。也就是说,线程每次访问都只能拿到这个唯一实例的引用,所以说饿汉式天生线程安全。

/**
* @Description 饿汉式单例类,加载时实例化
* @Date 2023/2/16 9:44
* @Version 1.0
*/
public class Singleton {

 private Singleton() {}

 private static final Singleton singleton = new Singleton();

 //静态工厂方法
 private static Singleton getInstance() {
     return singleton;
 }
}

6、请你说说乐观锁和悲观锁?

1、悲观锁:
就是很悲观,每次拿数据的时候都会认为别人会修改。所以每次拿数据的时候都会上锁。这样其他人想拿数据就拿不到,直到悲观锁被释放,悲观锁中的共享资源每次只给一个线程使用,其他线程阻塞,用完之后再把资源转给其它线程。阻塞式加锁
但是在效率这一方面,处理加锁的机制会产生额外的开销,还有增加死锁的机会。另外会降低并行性,如果已经锁定了一个线程A,其它线程就必须等待该线程A处理完才可以处理。
数据库中的行锁、表锁、读锁、写锁,以及synchronized实现的锁均为悲观锁
悲观并发控制实际上就是“先取锁再访问”的保守策略,多用于写比较多的场景,避免频繁失败和重试影响性能。

2、乐观锁
就是很乐观,每次拿数据的时候都认为别人不会更改。所以不会上锁,注意,乐观锁不会上锁,但是如果想要更新数据,则会在更新前检查在读取至更新这段时间别人有没有修改过这个数据,修改过,则进行自旋,再次尝试更新。直到更新成功。乐观锁适用于多读的应用类型,这样可以提高吞吐量。
相对于悲观锁,在对数据库进行处理时,乐观锁并不会适用数据库提供的锁机制。一般实现乐观锁的方式就是记录数据版本或者时间戳来实现,不过使用版本记录是最常用的。

7、请你说一说进程和线程的区别?

地址空间、开销、并发性、内存
主要差别在于它们是不同的操作系统资源的管理方式。
1、 进程有独立的地址空间,线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间
2、 进程和线程切换时,需要切换进程和线程的上下文,进程的上下文切换时间开销远远大于线程切换时间
3、 进程的并发性较低,线程的并发性高,为什么这样说呢,还是和开销有关系,并发的概念是将一个操作分割成多个部分执行并且无序处理,各部分之间可以随时切换,而进程切换的开销大,线程切换的开销小,所以说进程的并发性较低,线程的并发性较高
4、 每个独立的进程有一个程序运行的入口、顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
5、 系统在运行的时候会为每个进程分配不同的内存空间,而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源
6、一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都要死掉。所以多进程要比多线程健壮

8、设计模式了解吗?

创建型模式:工厂模式、单例模式
1、 单例模式:一个类有且仅有一个实例,并且自行实例化提供给整个系统使用
2、工厂模式:工厂模式的目的是将创建对象的具体过程屏蔽隔离起来,从而达到更高的灵活性,工厂模式可以分为三类:
a. 简单工厂模式
b. 工厂方法模式
c. 抽象工厂模式

9、请你讲一讲工厂模式?

工厂模式的目的是将创建对象的具体过程屏蔽隔离起来,从而达到更高的灵活性,工厂模式可分为三类:简单工厂模式、工厂方法模式、抽象工厂模式。
1、简单工厂模式:由一个工厂对象决定创建出哪一种产品类的实例,是工厂模式家族中最简单实用的模式。可以理解为不同工厂模式的一个特殊实现。即用户提需求,工厂去实现。
产品类

/**
 * @Description 产品类
 * @Date 2023/2/16 11:16
 * @Version 1.0
 */
//餐厅抽象类
public abstract class Restaurant {
    //菜品
    abstract String getName();
}

//红烧肉类:继承餐厅类,重写getName方法
class Pork extends Restaurant {
    @Override
    String getName() {
        return "红烧肉";
    }
}

//老鸭汤类:继承餐厅类,重写getName方法
class Duck extends Restaurant {
    @Override
    String getName() {
        return "老鸭汤";
    }
}

工厂类

public class Factory {
    public Restaurant create(String type) {
        switch (type) {
            case "pork":
                return new Pork();
            case "duck":
                return new Duck();
            default:
                break;
        }
        return null;
    }
}

用户类

public class Customer {
    public static void main(String[] args) {
        Factory factory = new Factory();
        Restaurant pork = factory.create("pork");
        Restaurant duck = factory.create("duck");
    }
}

简单工厂模式优缺点:
1)优点:由工厂类创建对象,用户不需要知道是怎么实现的,只用传入相应参数,进行了一定程度的解耦,提高了系统的灵活性。
2)缺点:不符合“开闭原则”,每次添加新产品就需要修改工厂类。在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展维护,因此出现了工厂方法模式。

2、工厂方法模式:此模式的核心就对封装的工厂类中易变化的部分,提取其中易变的部分成为独立类,通过依赖注入以达到解耦、复用和方便后期维护的目的。

产品类与上面相同

工厂类

public interface Factory {
    Restaurant create();
}

class FactoryPork implements Factory {
    @Override
    public Pork create() {
        return new Pork();
    }
}

class FactoryDuck implements Factory{
    @Override
    public Duck create() {
        return new Duck();
    }
}

用户类

public class Customer {
    public static void main(String[] args) {

        FactoryPork factoryPork = new FactoryPork();
        Pork pork = factoryPork.create();

        FactoryDuck factoryDuck = new FactoryDuck();
        Duck duck = factoryDuck.create();
    }
}

3、抽象工厂模式:抽象工厂模式对于工厂方法模式来说,就是工厂方法模式是针对一个产品系列的,而抽象工厂模式是针对多个产品系列的。
产品类

//发动机以及型号  
public interface Engine {}  
 
public class EngineA implements Engine{  
    public EngineA(){  
        System.out.println("制造-->EngineA");  
    }  
}  
public class EngineB implements Engine{  
    public EngineB(){  
        System.out.println("制造-->EngineB");  
    }  
}  
 
 
//空调以及型号  
public interface Aircondition {} 
 
public class AirconditionA implements Aircondition{  
    public AirconditionA(){  
        System.out.println("制造-->AirconditionA");  
    }  
}  
public class AirconditionB implements Aircondition{  
    public AirconditionB(){  
        System.out.println("制造-->AirconditionB");  
    }  
} 

工厂类

//创建工厂的接口  
public interface AbstractFactory {  
    //制造发动机
    public Engine createEngine();
    //制造空调 
    public Aircondition createAircondition(); 
}  
 
//为宝马320系列生产配件  
public class FactoryBMW320 implements AbstractFactory{     
    @Override  
    public Engine createEngine() {    
        return new EngineA();  
    }  
    @Override  
    public Aircondition createAircondition() {  
        return new AirconditionA();  
    }  
}  
//宝马523系列
public class FactoryBMW523 implements AbstractFactory {  
     @Override  
    public Engine createEngine() {    
        return new EngineB();  
    }  
    @Override  
    public Aircondition createAircondition() {  
        return new AirconditionB();  
    }  
} 

用户类

public class Customer {  
    public static void main(String[] args){  
        //生产宝马320系列配件
        FactoryBMW320 factoryBMW320 = new FactoryBMW320();  
        factoryBMW320.createEngine();
        factoryBMW320.createAircondition();
          
        //生产宝马523系列配件  
        FactoryBMW523 factoryBMW523 = new FactoryBMW523();  
        factoryBMW523.createEngine();
        factoryBMW523.createAircondition();
    }  
}

10、说说你对反射的了解?

反射概念、通过反射能实现什么
1、反射概念:动态获取程序信息以及动态调用对象,是实现动态语言的关键。
2、通过反射,我们能在程序运行期间,对于任意一个类,都能知道它所有的方法和属性,对于任意一个对象,都能知道它的属性和方法。
3、反射的本质就是把java类中的各种成分映射成一个个java对象
4、获取Class对象的三种方式:

		package fanshe;
		/**
		 * 获取Class对象的三种方式
		 * 1 Object ——> getClass();
		 * 2 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
		 * 3 通过Class类的静态方法:forName(String  className)(常用)
		 *
		 */
		public class Fanshe {
			public static void main(String[] args) {
				//第一种方式获取Class对象  
				Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
				Class stuClass = stu1.getClass();//获取Class对象
				System.out.println(stuClass.getName());
				
				//第二种方式获取Class对象
				Class stuClass2 = Student.class;
				System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个
				
				//第三种方式获取Class对象
				try {
					Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
					System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
				
			}
		}

5、优缺点:
优点:运行期间能够动态获取类,提高代码灵活性
缺点:性能比直接的java代码慢很多
6、应用场景:
使用JDBC时,如果要创建数据库的连接,则需要先通过反射机制加载数据库的驱动程序。
大多框架都支持注解/xml配置,从配置中解析出来的类是字符串,需要利用反射机制进行实例化。
面向切面编程(AOP)的实现方案就是在程序运行时创建目标对象的代理类,这就必须通过反射实现。

11、请你说一说ArrayList和LinkedList的区别

数据结构、访问效率
1、ArrayList是基于数组实现的、LinkedList是基于双向链表实现的
2、对于随机访问ArrayList要优于LinkedList,ArrayList可以通过下标对元素进行随机访问,时间复杂度是O(1),而LinkedList的每一个元素都依靠地址指针和它后一个元素连接在一起,查找某个元素的时间复杂度是O(N)
3、对于插入和删除操作,LinkedList要优于ArrayList,业务当元素添加到LinkList任意位置的时候,不需要像ArrayList那样重新计算大小或者更新索引。
4、LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素

12、说说你了解的线程同步方式

synchronized、Lock[lɑːk]
1、synchronized加锁的三种方式:
a、对于普通方法,锁的是当前实例对象

	public class SynchronizedTest {
	    public synchronized void test(String name){
	        System.out.println(name+"开始执行");
	        try {
	            Thread.sleep(2000);
	        }catch (Exception e){
	        }
	        System.out.println(name+"执行完毕");
	    }
	    public static void main(String[] args) throws  Exception {
	        SynchronizedTest t=new SynchronizedTest();
	        new Thread(new Runnable() {
	            @Override
	            public void run() {
	                t.test("线程1");
	            }
	        }).start();
	        SynchronizedTest t1=new SynchronizedTest();
	        new Thread(new Runnable() {
	            @Override
	            public void run() {
	                t1.test("线程2");
	            }
	        }).start();
	    }
	}

打印结果如下

线程1开始执行
线程2开始执行
线程2执行完毕
线程1执行完毕

b、对于静态同步方法,锁的是当前类的class对象

	public class SynchronizedTest {
	    public synchronized static void test(String name){
	        System.out.println(name+"开始执行");
	        try {
	            Thread.sleep(2000);
	        }catch (Exception e){
	        }
	        System.out.println(name+"执行完毕");
	    }
	    public static void main(String[] args) throws  Exception {
	        new Thread(new Runnable() {
	            @Override
	            public void run() {
	                SynchronizedTest.test("线程1");
	            }
	        }).start();
	        new Thread(new Runnable() {
	            @Override
	            public void run() {
	                SynchronizedTest.test("线程2");
	            }
	        }).start();
	    }
	}

打印结果如下:

线程1开始执行
线程1执行完毕
线程2开始执行
线程2执行完毕

c、对于同步代码块,锁的是synchronized括号里配置的对象

	public class SynchronizedTest {
	    public  void test(String name){
	       Object o=new Object();
	        synchronized(o.getClass()){
	            System.out.println(name+"开始执行");
	            try {
	                Thread.sleep(2000);
	            }catch (InterruptedException e){
	                e.printStackTrace();
	            }
	
	            System.out.println(name+"执行完毕");
	        }
	    }
	    public static void main(String[] args) throws  Exception {
	        SynchronizedTest t=new SynchronizedTest();
	        new Thread(new Runnable() {
	            @Override
	            public void run() {
	                t.test("线程1");
	            }
	        }).start();
	        SynchronizedTest t1=new SynchronizedTest();
	        new Thread(new Runnable() {
	            @Override
	            public void run() {
	                t1.test("线程2");
	            }
	        }).start();
	    }
	}

打印结果如下:

线程1开始执行
线程1执行完毕
线程2开始执行
线程2执行完毕

2、synchronized采用“CAS+Mark Word”实现,为了性能的考虑,并通过锁升级机制降低锁的开销。在并发环境中,synchronized会随着多线程竞争的加剧,按照如下步骤逐步升级:无锁、偏向锁、轻量级锁、重量级锁。
3、Lock则采用“CAS+volatile”实现,其实现的核心是AQS。AQS是线程同步器,是一个线程同步的基础框架,它基于模板方法模式。在具体的Lock实例中,锁的实现是通过继承AQS来实现的,并且可以根据锁的使用场景,派生出公平锁、不公平锁、读锁、写锁等具体的实现。

13、String、StringBuffer、Stringbuilder有什么区别?

1、 String是一个不可变类,即一旦一个String对象被创建以后,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁。
举一个栗子:

		String a = "123";
		a = "456";
		// 打印出来的a为456
        System.out.println(a)

任何对字符串进行修改的操作,并不是对原来堆中的实例对象进行重新赋值,而是生成一个新的实例对象,并且指向"456"这个字符串,a则指向最新生成的实例对象,之前的实例对象仍然存在,如果没有被再次引用,则会被垃圾回收。

2、StringBuffer对象则代表一个字符序列可变的字符串,当一个StringBuffer被创建以后,通过StringBuffer提供的append()、insert()、setCharAt()、setLength()等方法可以改变这个字符串对象的字符序列。一旦通过StringBuffer生成了最终想要的字符串,就可以调用它的tostring方法,将其转换成一个String对象

		StringBuffer b = new StringBuffer("123");
		b.append("456");
		// b打印结果为:123456
       System.out.println(b);

3、StringBuilder类也代表可变字符串对象。实际上,StringBuilder和StringBuffer基本类似。不同的是StringBuffer在所实现的方法上加了锁,是线程安全的。

14、请你说说HashMap底层原理?

数据结构、put()流程、扩容机制
1、 在JDK8中,HashMap底层是采用“数组+链表+红黑树”来实现的。 HashMap是基于哈希算法来确定元素的位置(槽)的,当我们向集合中存入数据时,它会计算传入的Key的哈希值,并利用哈希值取余来确定槽的位置。如果元素发生碰撞,也就是这个槽已经存在其他的元素了,则HashMap会通过链表将这些元素组织起来。如果碰撞进一步加剧,某个链表的长度达到了8,则HashMap会创建红黑树来代替这个链表,从而提高对这个槽中数据的查找的速度。 HashMap中,数组的默认初始容量为16,这个容量会以2的指数进行扩容。具体来说,当数组中的元素达到一定比例的时候HashMap就会扩容,这个比例叫做负载因子,默认为0.75。自动扩容机制,是为了保证HashMap初始时不必占据太大的内存,而在使用期间又可以实时保证有足够大的空间。采用2的指数进行扩容,是为了利用位运算,提高扩容运算的效率。
2、put()流程 put()方法的执行过程中,主要包含四个步骤:
a、 判断数组,若发现数组为空,则进行首次扩容。
b、 判断头节点,若发现头节点为空,则新建链表节点,存入数组。
c、 判断头节点,若发现头节点非空,则将元素插入槽内。
d、 插入元素后,判断元素的个数,若发现超过阈值则再次扩容。 其中,第3步又可以细分为如下三个小步骤:
1)若元素的key与头节点一致,则直接覆盖头节点。
2)若元素为树型节点,则将元素追加到树中。
3)若元素为链表节点,则将元素追加到链表中。追加后,需要判断链表长度以决定是否转为红黑树。若链表长度达到8、数组容量未达到64,则扩容。若链表长度达到8、数组容量达到64,则转为红黑树。
3、扩容机制 向HashMap中添加数据时,有三个条件会触发它的扩容行为: 1. 如果数组为空,则进行首次扩容。 2. 将元素接入链表后,如果链表长度达到8,并且数组长度小于64,则扩容。 3. 添加后,如果数组中元素超过阈值,即比例超出限制(默认为0.75),则扩容。 并且,每次扩容时都是将容量翻倍,即创建一个2倍大的新数组,然后再将旧数组中的数组迁移到新数组里。
4、HashMap是非线程安全的,在多线程环境下,多个线程同时触发HashMap的改变时,有可能会发生冲突。所以,在多线程环境下不建议使用HashMap,可以考虑使用Collections将HashMap转为线程安全的HashMap,更为推荐的方式则是使用ConcurrentHashMap。

15、详细说说Redis的数据类型?

1、Redis提供了五种数据结构:字符串(string)、哈希(hash)、列表(list)、集合(set)、有序集合(zset)
2、string可以存储字符串、数字和二进制数据,除了值可以是string以外,所有的键也可以是string,string最大可以存储大小为2m的数据
3、list保证数据线性有序可重复,它支持lpush、blpush、rpop、brpop等操作,可以当做简单的消息队列来使用
4、set是无序不可重复的,它支持多个set求交集、并集、差集
5、zset是有序不可重复的,它通过给每一个元素设置一个分数来实现排序

16、请你说说聚簇索引和非聚簇索引?

1、两者的区别主要是数据和索引是否分离。
2、聚簇索引是将数据和索引存储到一起,找到了索引也就找到了数据,而非聚簇索引是将数据和索引存储分离开,索引树的叶子节点存储了数据行的地址。

17、说说缓存穿透、击穿、雪崩?

1、缓存穿透:客户端访问缓存中不存在的数据,使得请求直达存储层,导致负载过大、甚至宕机。原因可能是业务层误删了缓存和库中的数据,或是有人恶意访问不存在的数据。解决方案:1)存储层未命中后,返回空值存入缓存层,客户端再次访问时,缓存层返回空值。
2)将数据存入布隆过滤器,访问缓存之前过滤器拦截,若请求的数据不存在就返回空值。
2、缓存击穿:一份热点数据,它的访问量是非常大的,在它缓存失败的瞬间,大量请求直达存储层,导致服务崩溃。解决方案:1)永不过期,对热点数据不设置过期时间。2)加互斥锁,当一个线程访问该数据时,另一个线程只能等待。
3、缓存雪崩:大量数据同时过期,或是redis节点故障导致服务不可用,缓存层无法提供服务,所有的请求直达存储层,造成数据宕机。
解决方案:1)避免数据同时过期,设置随机过期时间。2)启用降级和熔断措施。3)设置热点数据永不过期。

18、重载和重写的区别?

重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。
重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类,如果父类方法访问修饰符为private,则子类不能重写该方法。

19、Java面向对象编程三大特性:封装、继承、多态

封装:抽取出某一类对象共有的属性和行为,然后封装在类里面,我们把这个抽取到编码实现的过程叫做封装。从代码复用角度看,当我们发现一段代码可以重复使用,我们就可以把这一段代码放在方法里面,这个过程也叫作封装。从代码安全角度,我们建议属性私有化,然后提供get/set方法。
继承:通过extends[ɪkˈstendz]关键字继承父类非private的属性和方法,可以添加新功能,也可以对父类的功能进行扩展。一个类只能继承一个父类。
多态:调用同一方法时呈现不同的结果。java中有两种形式实现多态:继承(多个子类对同一方法的重写)、接口(实现接口并覆盖接口中的同一方法)

20、== 与 equals

1、==:基本数据类型比较的是值,引用数据类型比较的是内存地址
2、equals:equals没有重写之前,等价于 ==,比较的是对象的地址;equals重写后;比较的是对象的内容。

21、请你说说hashCode()和equals()的区别,为什么重写equals()就要重写hashcod()

hashCode()用途,equals()用途,hashCode()、equals()约定
1、hashCode():获取哈希码,equals():比较两个对象是否相等。
2、二者两个约定:如果两个对象相等,它们必须有相同的哈希码;若两个对象的哈希码相同,他们却不一定相等。也就是说,equals()比较两个对象相等时hashCode()一定相等,hashCode()相等的两个对象equqls()不一定相等。
3、加分回答:由于hashCode()与equals()具有联动关系,equals()重写时,hashCode()进行重写,使得这两个方法始终满足相关的约定。

22、请你说一说final关键字

1、final修饰的类不能被继承
2、final修饰的方法不能被重写
3、final修饰的常量不能被改变
4、final修饰的引用类型地址不可变,值可变
5、final修饰的方法和属性变量会被JVM优化,效率更高
6、final修饰的属性变量等,可以防止多线程数据安全问题

22、请你说说你对IOC的理解?

控制反转和依赖注入含义
1、IOC是控制反转的意思,是一种面向对象编程的设计思想。
2、不使用IOC的话我们需要自己维护对象与对象之间的依赖关系,很容易导致耦合度过高。使用IOC后,它可以帮我们维护对象与对象之间的依赖关系,降低耦合度。
3、IOC里面包含DI,DI是依赖注入的意思,它是IOC实现的实现方式。
4、将创建对象的权限转交给spring,不用我们自己创建对象,直接从spring容器里面取。

23、请你说说ConcurrentHashMap?

数组+链表+红黑树、锁的粒度
数据结构就和hashmap一样。主要说说它是怎么保障线程安全的。
1、初始化数组或者头结点时,ConcurrentHashMap并没有加锁,而是CAS的方式进行原子替换。
2、插入数据时会进行加锁处理,但锁定的不是整个数组,而是哈希槽中的头结点。所以锁的粒度是槽,而不是整个数组,并发性很好。
3、扩容是会进行加锁处理,锁定的仍是头结点。
4、查找数据时并不会进行加锁,所以性能很好。

24、请你说说MySQL索引,以及它们的好处和坏处

检索效率、存储资源、索引维护
1、MySQL索引是一种帮助快速查找数据的数据结构,可以把它理解为书的目录,通过索引能够快速找到数据所在位置。
2、在大数据量的查询中,合理使用索引的优点非常明显,不仅能大幅提高匹配where条件的检索效率,还能用于排序和分组操作的加速。
3、但是由于索引需要存储到磁盘,增加了存储的压力,并且新增数据时需要同步维护索引。

25、请讲一下常见的SQL优化方法

1、避免使用select * ,只查询需要的字段
2、尽量使用union all代替union,sql使用union后,会排除重复的数据,排重会消耗时间和资源,为了节省资源,尽量使用union all。
3、小表驱动大表:
假如有order和user两张表,其中order表有10000条数据,而user表有100条数据。时如果想查一下,所有有效的用户下过的订单列表。可以使用in关键字实现:

select * from order
where user_id in (select id from user where status=1)

也可以使用exists关键字实现:

select * from order
where exists (select 1 from user where order.user_id = user.id and status=1)

上面那个业务显然用in比较合适,因为in是先执行子查询语句,符合小表驱动大表,而exists是先执行主查询语句,不符合
4、SQL语句中IN包含的值不宜过多,对于较少的连续的值,能用between就不要用in了。
5、当只需要一条数据的时候,用limit 1.
6、如果限制条件中其他字段没有索引,尽量少用or

26、请讲一下MySQL数据库innodb引擎和myisam引擎索引的区别

1、innodb使用的是聚簇索引,将主键组织到一棵B+树中,而行数据就存储在叶子节点上,若使用where条件查询查询主键,则按照B+树的检索算法即可查找到对应的叶子节点,之后获的行数据。若是使用非主键字段进行条件查询,则需要两个步骤:第一步在辅助索引B+树中检索字段,到达其叶子节点获取对应的主键。第二步使用主键在主索引B+树中再执行一次B+树检索操作,由此获取叶子节点的整行数据。

2、myisam引擎使用的是非聚簇索引,非聚簇索引的两棵B+树节点的结构完全一致,只是存储的内容不同,主键索引B+树的节点存储了主键,辅助键索引B+树存储了辅助键,这两棵B+树的叶子节点都使用一个地址指向真正的表数据。由于索引树是独立的,通过辅助键检索无需访问主键的索引树。

27、请讲一下MySQL索引在哪些情况下会失效

1、索引无法存储null值
2、不适合键值较少的列(重复数据较多的列)
3、如果条件中有or,即使其中有条件带索引也不会使用
4、like查询以%开头
5、如果MySQL使用全表扫描要比使用索引快,则不使用索引
6、使用 is null 或 is not null也会限制索引的使用

28、JDK代理与CGLIB代理的区别

1、JDK动态代理:利用拦截器(拦截器必须实现InvacationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
2、CGLIB动态代理:利用ASM开源包,把代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。实现CGLIB动态代理必须实现MethodInterceptor(方法拦截器)接口。
3、JDK只能针对接口不能针对类实现代理,要求被代理的类必须实现至少一个接口。
4、CGLIB通过继承方式实现代理。所以类和方法最好不要声明为final,对于final类或方法,是无法继的承的,生成的代理类是被代理类的子类,所以CGLIB也被称作子类代理。

29、说说bean的生命周期

spring bean生命周期的四大部分以及详细步骤
1、Bean的生命周大致分为Bean的创建、Bean的初始化、Bean的调用、Bean的销毁。
2、spring启动时,查找并加载需要被spring管理的bean,进行bean的实例化。
3、Bean实例化后将Bean的引入和值注入到Bean的属性中。
4、如果Bean实现了BeanNameAware接口的话,spring将Bean的id传递给setBeanName()方法
5、如果BeanFactoryAware接口的话,spring将调用setBeanFactory()方法,将BeanFactory()容器实例传入。
6、如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。
7、如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。
8、如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用 。
9、如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。
10、此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
11、如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。

30、同步和异步的区别

1、同步:可以理解为在执行完一个函数或方法之后,一直等待系统返回值或消息,这是程序是处于阻塞的,只有接收到返回的值或消息才往下执行其他的命令。
2、异步:执行完函数或方法后,不必阻塞性等待返回值或消息,只需要系统委托一个异步过程,那么当系统接收到返回值或消息时,系统会自动触发委托的异步过程,从而完成一个完整的流程。

31、线程的创建方式

1、继承Thread,重写run()方法

public class Main {
    public static void main(String[] args) {
        new MyThread().start();
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
    }
}

2、实现Runnable接口

public class Main{
public static void main(String[]args){
new Thread(()->System.out.println(Thread.currentThread().getName()+"\t"+Thread.currentThread().getId())).start();
 }
}

3、实现Callable接口,重写call()方法,有返回值。

public class Main {
    public static void main(String[] args) throws Exception {
    	 // 将Callable包装成FutureTask,FutureTask也是一种Runnable
        MyCallable callable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        new Thread(futureTask).start();

        // get方法会阻塞调用的线程
        Integer sum = futureTask.get();
        System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum);
    }
}


class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tstarting...");

        int sum = 0;
        for (int i = 0; i <= 100000; i++) {
            sum += i;
        }
        Thread.sleep(5000);

        System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tover...");
        return sum;
    }
}

32、wait与sleep的区别

1、 sleep()是Thread类的方法;wait()是Object类的方法
2、sleep()没有释放锁,wait()释放了锁
3、wait()只能在同步控制方法和同步控制代码块中使用;sleep()可以在任何地方使用

33、请解释什么是脏读、幻读、不可重复读、第一类丢失更新以及第二类丢失更新

1、脏读:A事务读取B事务尚未提交的更改数据,并在这个数据的基础上进行操作,这时候如果B事务进行回滚,那么A事务读到的数据就是不被承认的。属于脏数据。例如常见的取款事务和转账事务。
2、不可重复读:不可重复读是指在一个事务内,多次读同一数据。事务A读取到张三的工资是5000,操作还没有完成,事务还没有提交;B事务把张三的工资改为了8000;这时事务A再次读取张三的工资,工资变成了8000,A事务两次读取的结果不一致。
3、事务1读取指定的where子句所返回的一些行。然后,事务2插入一个新行,这个新行也满足事务1使用的查询
where子句。然后事务1再次使用相同的查询读取行,但是现在它看到了事务2刚插入的行。这个行被称为幻象,
因为对事务1来说,这一行的出现是不可思议的。

注意:不可重复读和幻读的区别是:前者是指读到了已经提交的事务的更改数据(修改或删除),后者是指读到了其他已经提交事务的新增数据。对于这两种问题解决采用不同的办法,防止读到更改数据,只需对操作的数据添加行级锁,防止操作中的数据发生变化;二防止读到新增数据,往往需要添加表级锁,将整张表锁定,防止新增数据(oracle采用多版本数据的方式实现)。
4、第一类丢失更新:A事务撤销时,把已经提交的B事务的更新数据覆盖了。
在这里插入图片描述这时取款事务A撤销事务,余额恢复为1000,这就丢失了更新。
5、第二类已经丢失更新:A事务覆盖B事务已经提交的数据,造成B事务所做的操作丢失。
在这里插入图片描述

34、什么是阻塞与非阻塞

1、阻塞和非阻塞关注的是程序在等待调用(消息,返回值)时的状态
2、阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
3、非阻塞调用指在不能立即得到结果之前,该调用不会阻塞该线程。

35、Mybatis $与#的区别

1、传入的参数在SQL中显示不同
#传入的参数在SQL中显示字符串,会对自动传入的数据加一个双引号

select id,name,age from student where id =#{id}

解析为:

select id,name,age from student where id ="1"

$传入的参数在SQL中显示传入的值:

select id,name,age from student where id =${id}

解析为:

select id,name,age from student where id =1

2、#可以防止SQL注入的风险(语句的拼接),但$ 无法防止SQL注入
3、$ 方式一般用于传入数据库对象,例如传入表名
4、大多数情况下还是经常使用#,一般能用#的就别用$ ;但是有些情况下必须用$ :例:MyBatis排序时使用order by 动态参数的时候需要注意,用$而不是#

36、start()方法和run()方法的区别

1、当程序调用start方法时,将会创建一个新线程去执行run方法中的代码。但是如果直接调用run方法的话,会直接在当前线程中执行run的代码,这里run就像一个普通方法一样,不会创建新线程。
2、当一个线程启动后,不能重复调用start,否则会报异常,但是可以重复调用run方法。
3、run方法必须是public的访问权限,返回类型为void。

37、synchronized实现原理

1、synchronized是通过对象内部的一个叫监视器锁(monitor)实现的,监视器锁的本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的。
2、 同步方法通过ACC_SYNCHRONIZED关键字隐式的对方法进行加锁。当线程要执行的方法被标注上ACC_SYNCHRONIZED时,需要先获取锁才能执行方法。
3、同步代码块通过执行命令monitorenter和monitorexit来进行加锁。当线程执行到monitorenter的时候要先获得锁,才能执行后面的方法。当线程执行到monitorexit的时候要释放锁。每一个对象自身维护者一个被加锁次数的计数器,当计数器不为0时,只有获得锁的线程才能再次获得锁。

38、volatile实现原理

volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的。观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

39、什么是第一、第二、第三范式

1、第一范式:第一范式列不可再分
2、第二范式:在第一范式的基础上消除的部分函数依赖
(学号+课程)->姓名;学号->姓名
3、第三范式:在第二范式的基础上消除了传递依赖
(学号)->(宿舍),宿舍!=学号,(宿舍)->(费用),费用!=宿舍

40、JDK8新特性

1、Lambda表达式
2、函数式接口
3、方法引用和构造器调用
4、stream API
5、新时间日期API
6、接口中的默认方法和静态方法

41、spring boot的启动流程

调用run方法,run方法执行流程
1、当Spring Boot项目创建完成后会默认生成一个Application的入口类,这个类中的run方法可以启动Spring Boot项目。
2、在run方法中,通过SpringApplication的静态方法,即run方法进行SpringApplication的实例化操作,然后再针对实例化对象调用另外一个run方法来完成整个项目的初始化和启动。
3、SpringApplication调用的run方法重点做了以下操作: - 获取监听器参数配置 - 打印Banner信息 - 创建并初始化容器 - 监听器发送通知。

42、简单介绍spring

1、spring框架包含众多模块,如Core、Testing、Data Access、Web Servlet等,其中Core是整个Spring框架的核心模块。
2、Core模块提供IOC容器、AOP功能,IOC和AOP是spring框架的核心。
3、IOC是控制反转的意思,这是一种面向对象编程的设计思想,即将创建对象的控制权转交给IOC容器,让IOC容器帮我们维护对象和对象之间的依赖关系,降低对象之间的耦合度。
4、IOC容器中包含了DI,DI是依赖注入的意思,它是IOC实现的实现方式。
5、AOP是面向切面编程思想,当满足条件的时候,会将该业务代码织入到我们指定的位置,如使用注解实现日志记录。

43、说说spring boot的自动装配

自动装配概念、自动装配流程
1、使用spring boot时,我们需要引入对应的Starters,spring boot启动时便会自动加载相关依赖,配置相应的初始化参数,以最快捷、简单的形式对第三方软件进行集成,这便是spring boot的自动配置功能。
2、整个自动装配流程:Spring Boot通过@EnableAutoConfiguration注解开启自动配置,加载spring.factories中注册的各种AutoConfiguration类,当某个AutoConfiguration类满足其注解@Conditional指定的生效条件(Starters提供的依赖、配置或Spring容器中是否存在某个Bean等)时,实例化该AutoConfiguration类中定义的Bean(组件等),并注入Spring容器,就可以完成依赖框架的自动配置。

44、Java中的引用有哪些类型

1、强引用Strong Reference:java中的引用默认就是强引用,任何一个对象的赋值操作就产生了对这个对象的强引用。强引用的特性是只要有强引用存在,被引用的对象就不会被垃圾回收。
2、软引用Soft Reference:软引用在java中有个专门的SoftReference类型,软引用的意思是只有在内存不足的情况下,被引用的对象才会被回收。

    @Test
    public void softReference(){
        Object obj = new Object();
        SoftReference<Object> soft = new SoftReference<>(obj);
        obj = null;
        log.info("{}",soft.get());
        System.gc();
        log.info("{}",soft.get());
    }

3、弱引用weak Reference:weakReference和softReference很类似,不同的是weekReference引用的对象只要垃圾回收执行,就会被回收,而不管是否内存不足。

    @Test
    public void weakReference() throws InterruptedException {
        Object obj = new Object();
        WeakReference<Object> weak = new WeakReference<>(obj);
        obj = null;
        log.info("{}",weak.get());
        System.gc();
        log.info("{}",weak.get());
    }

4、虚引用PhantomReference:PhantomReference的作用是跟踪垃圾回收器收集对象的活动,在GC的过程中,如果发现有PhantomReference,GC则会将引用放到ReferenceQueue中,由程序员自己处理,当程序员调用ReferenceQueue.pull()方法,将引用出ReferenceQueue移除之后,Reference对象会变成Inactive状态,意味着被引用的对象可以被回收了。

45、说说你对JVM的了解

跨平台、HotSpot、内存模型
1、JVM是Java语言跨平台的关键,Java在虚拟机层面隐藏了底层技术的复杂性以及机器与操作系统的差异性。
2、JVM一般指Java虚拟机,JVM由三部分组成:类加载子系统、执行引擎、运行时数据区。

46、说说JVM的垃圾回收(GC)机制

垃圾回收机制主要完成三件事情:
1、哪些内存需要回收:废弃的常量,不再被引用的对象等
2、什么时候回收:当类不再被使用
3、如何回收:用引用计数法判断对象是否存活,采用垃圾收集器进行回收

47、说说JVM的垃圾回收算法

1、标记复制算法:将内存分为两块,每次只使用其中一块,当这块内存用完,就将还活着的对象复制到另外一块上面,效率高且没有碎片,但是需要双倍的空间。
2、标记清除法:先标记需要清除的对象,然后统一回收这些对象,不需要额外的空间,但是需要两次扫描耗时严重并且会产生内存碎片。
3、标记整理法:标记存活对象,然后将标记的存活对象按内存地址依次排序,清除边界外未标记的对象,没有内存碎片。

48、说说你了解的JVM

类加载子系统、执行引擎、运行时数据区
1、JVM由三部分组成:类加载子系统、执行引擎、运行时数据区
2、类加载子系统:可以根据指定的全限定名来载入类或接口
3、执行引擎:负责执行那些包含在被载入类的方法中的指令
4、当程序运行时,JVM需要内存来存储许多内容。

49、JVM如何判断对象是否可以被回收

1、引用计数法:就是在对象被引用时,计数加1,引用断开时,计数减1。那么一个对象的引用计数为0时,说明这个对象可以被清除。
这个算法的问题在于,如果A对象引用B的同时,B对象也引用A,即循环引用,那么虽然双方的引用计数都不为0,但如果仅仅被对方引用实际上没有存在的价值,应该被GC掉。
2、可达性算法:通过引用计数法的缺陷可以看出,从被引用一方去判定其是否应该被清理过于片面,所以我们可以通过相反的方向去定位对象的存活价值:一个存活对象引用的所有对象都是不应该被清除的(Java中软引用或弱引用在GC时有不同判定表现,不在此深究)。这些查找起点被称为GC Root

50、SpringMVC执行流程

1、客户端发送请求至DispatcherServlet。
2、DispatcherServlet收到请求后,根据请求URL找到对应的Controller。
3、Controller接收请求后,调用Service层处理请求。
4、Service层处理请求后,将数据返回给Controller。
5、Controller将处理结果返回给DispatcherServlet。
6、DispatcherServlet将处理结果返回给客户端。
在执行流程中,DispatcherServlet是核心控制器,负责协调各个组件的工作,并根据请求URL找到对应的Controller。Controller负责接收请求并调用Service层处理请求。Service层处理请求并将数据返回给Controller。最终,Controller将处理结果返回给DispatcherServlet,DispatcherServlet将处理结果返回给客户端。

51、MyBatis 中的延迟加载和饥饿加载的区别在于

1、延迟加载(Lazy Loading):在需要时才加载数据,即当访问一个对象的关联对象时才会真正发出 SQL 语句去查询。
2、饥饿加载(Eager Loading):在查询时立即加载数据,即在查询主对象时同时查询出关联对象。
3、延迟加载常用于加载大量数据时,以节约内存;而饥饿加载常用于需要立即访问关联对象的情况。你可以通过配置 XML 文件或注解来设置加载方式。

52、利用redis实现一个分布式锁

1、使用SET命令尝试将锁的key设置为某个值(可以是任意值)。
2、如果SET命令返回OK,说明锁已经被获取。此时,执行业务代码逻辑。
3、当业务代码执行完成后,使用DEL命令删除锁的key。
4、需要注意的是,在使用SET命令时,可以设置NX(只有在key不存在时才设置)和EX(key的过期时间)参数,以保证锁的正确性和可靠性。
另外,由于分布式环境下的竞争情况比较复杂,需要考虑多种情况,如锁的超时机制、误删问题等,建议使用可靠的第三方分布式锁组件来实现分布式锁。

53、工厂模式使用场景

1、在任何需要生成复杂对象的地方,都可以使用工厂方法模式.只有复杂的对象才适用于工厂方法模式.对于简单的只要通过new就可以完成创建的对象,无需使用工厂模式.如果简单对象使用工厂模式,需要引入一个工厂类,增加系统的复杂度。
2、工厂模式是一种典型的解耦模式,当类之间需要增加依赖关系时,可以使用工厂模式降低系统之间的耦合度
3、工厂模式是依靠抽象架构的,将实例化的任务交给子类实现,扩展性好.当系统需要较好的扩展性时,可以使用工厂模式,不同的产品使用不同的工厂来实现组装。

54、单例模式的使用场景

单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如:
1.需要频繁实例化然后销毁的对象。
2.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
3.有状态的工具类对象。
4.频繁访问数据库或文件的对象。

55、请你讲一讲JVM内存

JVM内存是一种计算机虚拟内存,它可以帮助程序员更好地管理计算机内存,提高程序的运行效率。JVM内存一般分为堆内存和栈内存,它们的作用分别是存储对象实例和支持方法调用。

56、数据库为什么不用红黑树而用B+树

磁盘IO
1、红黑树近似于平衡二叉树,节点是非黑即红的,时间复杂度为O(n),无论增删改查,性能都十分稳定,但是红黑树的本质还是二叉树,在数据量非常大的时候,访问数据会进行多次磁盘IO,导致效率较低。
2、而B+树是多叉的,可以有效减少磁盘IO次数;同时B+树增加了叶子节点间的连接,能保证范围查找时找到起点和终点从而快速取出需要的数据。

57、HashSet如何检查重复

当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是,如果发现有相同hashcode值的对象,这时就会调用equals方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。

58、Redis的持久化

1、Redis的持久化指的是将内存中的数据保存到磁盘上,以便在Redis重启后可以恢复数据。Redis支持两种不同的持久化方式:RDB和AOF。
2、RDB持久化方式是将Redis在内存中的数据定期保存到磁盘上,以便在Redis重启后可以快速地将数据恢复到内存中。而AOF持久化方式则是将Redis执行的每条写命令追加到一个日志文件中,以便在Redis重启后重新执行这些命令以恢复数据。
3、可以根据实际需求选择RDB或AOF持久化方式,或者同时使用这两种方式进行持久化。

59、Redis的主从复制

Redis主从复制是一种Redis数据库的数据同步机制,它允许将一个Redis服务器的数据自动复制到一个或多个其他Redis服务器上,从而实现数据的冗余备份和负载均衡。在Redis主从复制中,有一个Redis服务器作为主服务器,负责接收客户端的写操作,并将写操作发送给从服务器进行复制。从服务器只能进行读操作,它会定期从主服务器获取最新的数据,并将数据同步到本地。Redis主从复制可以提高Redis的性能和可靠性,同时也可以实现Redis的高可用性。

60、RabbitMQ死信队列

RabbitMQ死信队列是指当消息无法被消费者正常处理时,会被重新发送到死信队列中。一般情况下,这些消息被认为是“无用”的,但在某些情况下可能会被重新处理。使用死信队列可以有效地避免消息被永久丢失,提高消息的可靠性和稳定性。

61、为什么要用Redis

高性能、高并发

62、Redis淘汰策略

redis的内存淘汰策略是指在redis用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。
1、全局的键空间选择性移除
1)noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
2)allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。(这个是最常用的)
3)allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
2、设置过期时间的键空间选择性移除
1)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
2)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
3)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

63、Redis如何与数据库保证双写一致性

1、先更新缓存再更新数据库:但这种操作消耗很大。
2、先更新数据库再更新缓存:多线程并发选会存在数据库中数据和缓存不一致的现象。
3、先删除缓存再更新数据库:也可能导致缓存和数据库数据不一致。
4、先更新数据库再删除缓存:如果失败,采用重试机制。

64、Redis如何避免消息丢失

1、利用Redis的主从同步,部署多个Redis实例,主库负责读、写操作;从库定期同步主库的数据
2、利用Redis提供的两种同步机制

65、Redis如何保证原子性

66、Redis过期策略

redis 过期策略是:定期删除+惰性删除。
1、所谓定期删除,指的是 redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。
假设 redis 里放了 10w 个 key,都设置了过期时间,你每隔几百毫秒,就检查 10w 个 key,那 redis 基本上就死了,cpu 负载会很高的,消耗在你的检查过期 key 上了。注意,这里可不是每隔 100ms 就遍历所有的设置过期时间的 key,那样就是一场性能上的灾难。实际上 redis 是每隔 100ms 随机抽取一些 key 来检查和删除的。
2、但是问题是,定期删除可能会导致很多过期 key 到了时间并没有被删除掉,那咋整呢?所以就是惰性删除了。这就是说,在你获取某个 key 的时候,redis 会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。获取 key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西。
3、但是实际上这还是有问题的,如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,咋整?
答案是:走内存淘汰机制。

67、服务熔断和服务降级

服务熔断和服务降级是微服务架构中常用的两种容错机制。服务熔断是指当某个服务出现故障或异常时,系统会自动切断对该服务的请求,从而避免故障的扩散。服务降级是指当系统出现高并发或资源不足等情况时,系统会自动关闭某些服务或功能,以保证核心功能的正常运行。这两种机制都可以提高系统的可用性和稳定性。

68、装箱和拆箱

装箱:基本类型转变为包装器类型的过程。
拆箱:包装器类型转变为基本类型的过程。
装箱是通过调用包装器类的 valueOf 方法实现的
拆箱是通过调用包装器类的 xxxValue 方法实现的,xxx代表对应的基本数据类型。
如int装箱的时候自动调用Integer的valueOf(int)方法;Integer拆箱的时候自动调用Integer的intValue方法。

69、i++与++i

1、a = i++的意思是,先把i的值赋给a,即a = i,再执行i = i + 1;
2、a = ++i是先执行 i = i+1,再把i的值赋给a;

70、Git指令

71、说说AOP以及它的实现原理

1、AOP:面向切面编程。在Java中,对象和对象之间,方法和方法之间,模块与模块之间都可以看做是一个切面。
2、比如说我们做活动的时候,活动一般都具有时效性,过了时效性的活动就不能报名和领取活动奖励了,这样活动的时效性判断的方法就有两个地方要是用,为了避免重复代码,我们就编写一个公共方法并把该方法注入接口需要调用的位置,也就是切点。
3、AOP实现原理:AOP是基于动态代理实现的,因此AOP支持局限于方法级别的拦截。AOP支持JDK动态代理和CGLIB动态代理。
4、默认情况下,实现了接口的类,使用AOP会基于JDK生成代理类
5、没有实现接口的类,会基于CGLIB生成代理类

72、gateway三大核心组件

Gateway 的三大核心组件是路由器、过滤器和负载均衡器。路由器用于将请求路由到正确的服务实例,过滤器用于对请求和响应进行处理,负载均衡器用于将请求分配到可用的服务实例上,以实现高可用性和性能优化。

73、gateway与zuul的区别

74、mysql索引结构

MySQL 索引结构是 B+ 树结构,它是一种基于磁盘存储的多叉树结构,可以快速定位到需要查询的数据。B+ 树的叶子节点存储了完整的数据记录,而非叶子节点只存储索引信息。这种结构可以提高查询效率,减少磁盘 I/O 操作。

75、线程的生命周期

76、数据库事务

77、mybatis的mapper的动态代理是怎么实现的

78、switch语句块里面的case的后面的参数有什么要求?

类型只能是整型、字符型、枚举类型enum

79、抽象类和接口的区别

抽象类:
构造方法:有构造方法,用于子类实例化使用。
成员变量:可以是变量,也可以是常量。
成员方法:可以是抽象的,也可以是非抽象的。
默认修饰符:default

接口:
构造方法:没有构造方法
成员变量:只能是常量。默认修饰符:public static final
成员方法:jdk1.7只能是抽象的。默认修饰符:public abstract (推荐:默认修饰符请自己永远手动给出)
jdk1.8可以写以default和static开头的具体方法

80、for循环条件

public class Test
{
	static boolean foo(char c)
	{
		System.out.print(c);
		return true;
	}
	public static void main( String[] argv )
	{
		int i = 0;
		for ( foo('A'); foo('B') && (i < 2); foo('C'))
		{
			i++ ;
			foo('D');
		}
	}
}

for循环里面不作为循环条件的条件都可以拿出去;如上面就可以把 foo(‘A’)、foo(‘C’)拿出去,上面输出结果:ABDCBDCB

81、常用Ascll码

空格:32;0:48;A:65;a:97

82、RabbitMQ 如何保证消息不丢失?

83、Object类下有几个方法

1、hashCode()和equals()
2、toString()和getClass()
3、wait(),wait(long),wait(long,int),notify(),notifyAll()

84、&&和&的区别

85、事务的隔离级别

86、简述Redis备份策略

87、session cookie application区别

Session cookie是一种用于在Web应用程序中跟踪用户会话的机制,它将一个唯一的标识符存储在用户的浏览器中,以便在用户访问站点时能够识别用户。Application是一个Web应用程序中的全局对象,它可以用于在不同的页面和会话之间共享数据。

88、udp和tcp的区别

89、高并发场景如何实现商品秒杀,简述你的设计思路

对于高并发场景下的商品秒杀,我的设计思路是采用分布式架构,将请求分散到多个服务器上进行处理,同时使用缓存技术和消息队列来提高系统的性能和可靠性。具体来说,可以采用以下措施:

使用缓存技术:将商品信息、用户信息等常用数据缓存在内存中,减少数据库的访问次数,提高系统的响应速度。

使用消息队列:将用户的请求放入消息队列中,异步处理,避免请求堆积,提高系统的并发能力。

限流措施:设置访问频率限制,防止恶意攻击和过度消耗系统资源。

数据库优化:使用数据库连接池、索引等技术来提高数据库的性能和并发能力。

分布式架构:将请求分散到多个服务器上进行处理,避免单点故障和系统崩溃。

通过以上措施的综合应用,可以有效地提高系统的性能和可靠性,实现高并发场景下的商品秒杀。

90、值传递和引用传递的区别

91、导入一个EXCEL200M的文件,有上百万条数据,但在导入的时候一直提示内存溢出,如果你碰到这个问题,你觉的是什么原因,怎么解决

这个问题可能是由于计算机内存不足导致的。解决方法可以尝试以下几种:1.增加计算机内存;2.将文件分割成多个较小的文件进行导入;3.使用专业的数据处理软件进行导入。

92、AOS

93、线程池实现原理、拒绝策略,核心线程数

线程池实现原理是通过维护一个线程池来管理线程的创建、销毁和复用,从而提高程序的性能和效率。拒绝策略是当线程池中的线程已经达到最大数量,无法再创建新的线程时,对新的任务的处理方式。常见的拒绝策略有AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy和DiscardPolicy。核心线程数是指线程池中最少的线程数量,即使线程池中没有任务需要处理,这些线程也会一直保持运行状态。

94、JVM内存结构

95、JVM垃圾回收机制

96、JVM优化参数配置

97、Redis的高可用原理

Redis的高可用原理是通过使用主从复制和哨兵机制来实现的,其中主节点负责写入数据,从节点负责读取数据,并且哨兵节点负责监控节点的状态并在节点故障时进行故障转移。

98、redis cluster

99、hash一致性算法

100、幂等性

101、如何保证分布式幂等性

102、简单说说面向对象的特征以及六大原则

103、谈谈 final、finally、finalize 的区别

104、wait() sleep() yield()方法的区别

105、JVM 性能调优的监控工具了解那些?

106、5 亿整数的大文件,怎么排?

107、full gc 怎么触发

108、ClassLoader 原理和应用

109、高吞吐量的话用哪种 gc 算法

110、1 亿个手机号码,判断重复

111、线程之间的交互方式有哪些?有没有线程交互的封装类

112、两次点击,怎么防止重复下订单

前端验证:在用户提交订单前,可以通过前端验证防止用户重复提交订单。
后端验证:在后端处理订单时,可以通过订单号等唯一标识来判断是否已经存在相同的订单,避免重复下单。
限制下单频率:可以设置一个下单频率限制,当用户下单频率超过限制时,禁止用户再次下单。

113、数据库表设计,索引

114、类加载机制(双亲委派模型)

类加载机制是Java虚拟机中的一个重要概念,它使用双亲委派模型来加载类,即先从父类加载器中查找类,如果找不到再从子类加载器中查找。

115、MQ消息重复发送、消息发送失败、长时间收不到消息

116、分库分表能解决什么问题

分库:将一个库的数据拆分到多个相同的库中,访问的时候访问一个库
分表:把一个表的数据放到多个表中,操作对应的某个表就行

117、TreeMap怎么实现排序

118、4 种线程池

119、基于线程池的方式

120、有三个线程 T1,T2,T3,如何保证顺序执行?

121、List常用方法

在这里插入图片描述

122、set常用方法

在这里插入图片描述

123、map常用方法

在这里插入图片描述

124、String常用方法

length();charAt(int index);substring(int beginIndex);equals(Object obj);equalsIgnoreCase(String str);toLowerCase();toUpperCase();trim();split(String str);valueOf(xxx xx);replace(char oldChar, charnewChar)

125、final finally finalize区别

1、final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表
示该变量是一个常量不能被重新赋值。
2、finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块
中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
3、finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调
用,当我们调用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的
最后判断。

126、说说你对线程池的理解

127、死锁怎么解决

1、预防死锁:通过合理的资源分配和调度算法,避免系统进入死锁状态。
2、避免死锁:通过银行家算法等方法,预测系统可能出现死锁的情况,提前采取措施避免死锁的发生。
3、检测死锁:通过资源分配图等方法,检测系统是否出现死锁,如果出现死锁,则采取相应的措施解除死锁。
4、解除死锁:通过资源剥夺、撤销进程等方法,解除系统中的死锁状态。

128、nignx

129、用arraylist存入100w数据,我要找一个数据,怎么快速查找

可以使用二分查找算法,在使用前需要先对arraylist进行排序,然后通过二分查找算法快速定位需要查找的数据。

130、说下tcp三次握手,四次挥手

131、nginx反向代理后面加/的区别和不加/的区别

132、索引覆盖和回表

索引覆盖是指查询语句只需要通过索引就可以获取所需的数据,而不需要回到数据表中查找。这样可以减少IO操作,提高查询效率。回表则是指查询语句需要通过索引找到数据行后,还需要回到数据表中查找其他需要的数据。这样会增加IO操作,降低查询效率

133、递归实现斐波拉契

134、fail-fast机制与 fail-safe机制

135、大量SQL 注入漏洞,如何解决

针对 SQL 注入漏洞,可以采取以下措施进行解决:
1、使用参数化查询,将用户输入的数据作为参数传入 SQL 语句中,而不是将用户输入的数据直接拼接到 SQL 语句中。
2、对用户输入的数据进行严格的验证和过滤,确保输入的数据符合预期的格式和类型,避免恶意用户利用 SQL 注入漏洞进行攻击。
3、对数据库进行安全加固,包括限制数据库用户的权限、加强数据库访问控制、定期备份数据库等措施,以减少 SQL 注入漏洞的影响。
4、定期对系统进行漏洞扫描和安全评估,及时发现和修复 SQL 注入漏洞,确保系统的安全性和稳定性。

136、nginx负载均衡的配置有哪些?

137、用户多次登录怎么处理

1、限制登录次数:可以设置一个登录次数限制,当用户登录次数超过限制时,禁止用户再次登录。
2、强制下线:当用户在其他设备上登录时,可以强制当前设备下线,保证用户只能在一个设备上登录。
3、记录登录信息:可以记录用户的登录信息,包括登录时间、IP地址等,以便后续分析和处理。

138、大量请求怎么处理?

139、session、sessionStorage、localStorage的区别

140、map遍历的三种方式

在这里插入图片描述

141、get和post的区别?以及什么是重定向?

142、mybatis是什么

143、数据库中的四种事务锁的区别与使用注意

1、共享锁(Shared Lock):多个事务可以同时持有共享锁,但是不能持有排他锁。共享锁用于读取操作,可以防止其他事务修改数据。
2、排他锁(Exclusive Lock):只有一个事务可以持有排他锁,其他事务不能持有任何锁。排他锁用于写入操作,可以防止其他事务读取或写入数据。
3、更新锁(Update Lock):更新锁是一种特殊的共享锁,用于在读取数据时防止其他事务修改数据。如果事务需要修改数据,则需要升级为排他锁。
4、意向锁(Intent Lock):意向锁是一种特殊的锁,用于表示事务将要持有的锁的类型。意向锁可以提高锁的效率,减少锁的冲突。

144、微服务有什么特点?谈谈你对微服务的了解(核心组件)

微服务的特点包括:松耦合、独立部署、可伸缩、容错性强、技术异构性、可替换性、自治性等。微服务的核心组件包括:服务注册与发现、负载均衡、服务网关、配置中心、断路器等。

145、JVM调优

堆内存调优、GC调优、线程调优、类加载调优等

146、Servlet的生命周期

初始化、服务、销毁三个阶段

147、JVM加载class文件的原理

将class文件加载到内存中,并将其转换为JVM可识别的格式,然后进行验证、准备、解析、初始化等步骤,最终生成可执行的代码。

148、假设公司一个数据的表中有100W数据,有一个积分字段,会在每年的第一天凌晨0点开始进行清零,如果你来处理这个业务,你会考虑什么?你会如何实现?

1、数据库设计:在设计数据库时,需要考虑积分字段的数据类型、索引等因素,以便快速查询和更新数据。
2、定时任务:需要编写定时任务,在每年的第一天凌晨点时,将积分字段清零。
3、备份和恢复:在清零操作前,需要备份数据,以便出现问题时可以及时恢复数据。
4、具体实现方案可以根据具体情况而定,例如可以使用 SQL 语句更新积分字段,也可以使用脚本编写定时任务等。

149、rabbitmq消费者宕机了怎么办

150、分布式为什么会死锁,怎么解决,怎么防止进行的时候自动过期

151、事务并发,如何解决第二类更新丢失

152、linux常见命令

153、mysql约束

154、是否遇到过消息积压?怎么处理消息丢失?

遇到消息积压时,可以考虑增加消息队列的容量或者优化消费者的处理能力。如果消息已经丢失,可以考虑使用消息确认机制,确保消息被正确地处理。此外,也可以考虑使用日志记录消息的处理情况,以便后续排查问题。

155、hystrix熔断机制

156、事务的默认传播机制是啥

157、mybatis-plus怎么分页的

158、Spring中异常的处理方式

159、jvm堆和栈

160、自己实现数据的解密,以及在高并发的情况下,多个线程来进行解密会出现问题吗?

在高并发的情况下,多个线程同时进行解密可能会出现问题,因为多个线程同时访问同一个解密函数可能会导致数据混乱或者解密失败。因此,在多线程环境下,需要采取合适的同步措施来保证解密的正确性。

161、SQL中怎么实现分页

162、我非要使用mybatis的$,那怎么解决sql注入的问题

你可以使用#来代替 $ ,这样就可以避免SQL注入的问题。#会将参数值作为字符串进行处理,而$会将参数值直接替换到SQL语句中,容易受到SQL注入攻击。另外,还可以使用预编译语句来避免SQL注入的问题。预编译语句会将SQL语句和参数分开处理,从而避免了SQL注入的风险。

163、利用POI框架实现百万数据的导入,分批次还是一次,事务传播性该如何控制,利用线程池如何配置参数,淘汰策略选择那个,配置好后如何进行代码的实现,一个线程执行之后如何立马让下一个线程接着执行

对于百万数据的导入,建议采用分批次导入的方式,这样可以避免一次性导入数据量过大导致系统崩溃的情况发生。在事务传播性方面,建议采用PROPAGATION_REQUIRED传播属性,这样可以保证整个导入过程中的事务一致性。在线程池方面,需要根据具体情况来配置参数,比如线程池大小、队列容量等。淘汰策略可以选择根据线程的空闲时间来进行淘汰。在代码实现方面,可以使用Java中的Executor框架来实现线程池的管理,通过submit方法提交任务,然后通过get方法获取任务执行结果。对于一个线程执行完毕后如何立即让下一个线程接着执行,可以使用CountDownLatch来实现。

164、有学生表student(id,name,sex,gpa),请列出男同学人数以及他们平均GPA。平均GPA保留一位小数

165、Java的基类中有哪些是可以继承的,哪些是不能继承的?static方法可以被子类覆盖吗?为什么?

Java的基类中可以被继承的包括普通方法和非私有变量,不能被继承的包括私有方法和变量。static方法不能被子类覆盖,因为它是与类相关而不是与实例相关的。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值