美文网首页Java
Java程序员必会的三个技能:Spring+MySQL+并发编程

Java程序员必会的三个技能:Spring+MySQL+并发编程

作者: Java斗帝之路 | 来源:发表于2020-10-31 14:45 被阅读0次

    并发编程、数据库和Spring,我认为这个算程序员开发面试的都必须接触到的。作为面试的热点及难点,一直深受面试官青睐都是离不开的。今天就聊一聊一些经典的题目,让大家对Spring、MySQL、并发有一定清楚的认知。每天看一看,开发不困难。
    [图片上传失败...(image-9c732-1604125149102)]

    一、并发编程

    另外本人整理了20年面试题大全,包含spring、并发、数据库、Redis、分布式、dubbo、JVM、微服务等方面总结,下图是部分截图,需要的话关注公众号【Java斗帝】回复“666”免费获取。

    image

    1、Java 中实现多线程有几种方法

    (1)继承 Thread 类;

    (2)实现 Runnable 接口;

    (3)实现 Callable 接口通过 FutureTask 包装器来创建 Thread 线程;

    (4)使用 ExecutorService、Callable、Future 实现有返回结果的多线程(也就是使用了 ExecutorService 来管理前面的三种方式)。

    2、如何停止一个正在运行的线程

    (1)使用退出标志,使线程正常退出,也就是当 run 方法完成后线程终止。

    (2)使用 stop 方法强行终止,但是不推荐这个方法,因为 stop 和 suspend 及 resume 一样都是过期作废的方法。

    (3)使用 interrupt 方法中断线程。

    class MyThread extends Thread {
        volatile Boolean stop = false;
        public void run() {
            while (!stop) {
                System.out.println(getName() + " is running");
                try {
                    sleep(1000);
                }
                catch (InterruptedException e) {
                    System.out.println("week up from blcok...");
                    stop = true;
                    // 在异常处理代码中修改共享变量的状态
                }
            }
            System.out.println(getName() + " is exiting...");
        }
    }
    class InterruptThreadDemo3 {
        public static void main(String[] args) throws InterruptedException {
            MyThread m1 = new MyThread();
            System.out.println("Starting thread...");
            m1.start();
            Thread.sleep(3000);
            m1.interrupt();
            // 阻塞时退出阻塞状态
            Thread.sleep(3000);
            // 主线程休眠 3 秒以便观察线程 m1 的中断情况
            System.out.println("Stopping application...");
        }
    }
    

    3、notify()和 notifyAll()有什么区别?

    notify 可能会导致死锁,而 notifyAll 则不会

    任何时候只有一个线程可以获得锁,也就是说只有一个线程可以运行 synchronized 中的代码使用 notifyall,可以唤醒所有处于 wait 状态的线程,使其重新进入锁的争夺队列中,而 notify 只能唤醒一个。

    wait() 应配合 while 循环使用,不应使用 if,务必在 wait()调用前后都检查条件,如果不满足,必须调用 notify()唤醒另外的线程来处理,自己继续 wait()直至条件满足再往下执行。

    notify() 是对 notifyAll()的一个优化,但它有很精确的应用场景,并且要求正确使用。不然可能导致死锁。正确的场景应该是 WaitSet 中等待的是相同的条件,唤醒任一个都能正确处理接下来的事项,如果唤醒的线程无法正确处理,务必确保继续 notify()下一个线程,并且自身需要重新回到 WaitSet 中。

    4、sleep()和 wait() 有什么区别?

    对于 sleep()方法,我们首先要知道该方法是属于 Thread 类中的。而 wait()方法,则是属于 Object 类中

    sleep()方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用 sleep()方法的过程中,线程不会释放对象锁。

    当调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。

    5、volatile 是什么?可以保证有序性吗?

    一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,那么就具备了两层语义:

    (1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,volatile 关键字会强制将修改的值立即写入主存。

    (2)禁止进行指令重排序。

    volatile 不是原子性操作

    什么叫保证部分有序性?

    当程序执行到 volatile 变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

    x = 2;//语句 1
    y = 0;//语句 2
    flag = true;//语句 3
    x = 4;//语句 4
    y = -1;//语句 5
    
    

    由于flag 变量为 volatile 变量,那么在进行指令重排序的过程的时候,不会将语句 3 放到语句 1、语句 2 前面,也不会讲语句 3 放到语句 4、语句 5 后面。但是要注意语句 1 和语句 2 的顺序、语句 4 和语句 5 的顺序是不作任何保证的。

    使用 Volatile 一般用于 状态标记量 和 单例模式的双检锁

    6、Thread 类中的 start() 和 run() 方法有什么区别?

    start()方法被用来启动新创建的线程,而且 start()内部调用了 run()方法,这和直接调用 run()方法的效果不一样。当你调用 run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。

    [图片上传失败...(image-ea72dd-1604125149104)]

    7、为什么 wait, notify 和 notifyAll 这些方法不在 thread 类里面?

    明显的原因是 JAVA 提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的 wait()方法就有意义了。如果 wait()方法定义在 Thread 类中,线程正在等待的是哪个锁就不明显了。简单的说,由于 wait,notify 和 notifyAll 都是锁级别的操作,所以把他们定义在 Object 类中因为锁属于对象。

    8、为什么 wait 和 notify 方法要在同步块中调用?

    (1)只有在调用线程拥有某个对象的独占锁时,才能够调用该对象的 wait(),notify()和 notifyAll()方法。

    (2)如果你不这么做,你的代码会抛出 IllegalMonitorStateException 异常。

    (3)还有一个原因是为了避免 wait 和 notify 之间产生竞态条件。

    wait()方法强制当前线程释放对象锁。这意味着在调用某对象的 wait()方法之前,当前线程必须已经获得该对象的锁。因此,线程必须在某个对象的同步方法或同步代码块中才能调用该对象的 wait()方法。

    在调用对象的 notify()和 notifyAll()方法之前,调用线程必须已经得到该对象的锁。因此,必须在某个对象的同步方法或同步代码块中才能调用该对象的 notify()或 notifyAll()方法。

    调用 wait()方法的原因通常是,调用线程希望某个特殊的状态(或变量)被设置之后再继续执行。调用 notify()或 notifyAll()方法的原因通常是,调用线程希望告诉其他等待中的线程:“特殊状态已经被设置”。这个状态作为线程间通信的通道,它必须是一个可变的共享状态(或变量)。

    9、Java 中 interrupted 和 isInterruptedd 方法的区别?

    interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。Java 多线程的中断机制是用内部标识来实现的,调用 Thread.interrupt()来中断一个线程就会设置中断标识为 true。当中断线程调用静态方法 Thread.interrupted()来检查中断状态时,中断状态会被清零。而非静态方法 isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出 InterruptedException 异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。

    10、Java 中 synchronized 和 ReentrantLock 有什么不同?
    相似点:

    这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的。

    区别:

    这两种方式最大区别就是对于 Synchronized 来说,它是 java 语言的关键字,是原生语法层面的互斥,需要 jvm 实现。而 ReentrantLock 它是 JDK 1.5 之后提供的 API 层面的互斥锁,需要 lock()和 unlock()方法配合 try/finally 语句块来完成。

    Synchronized 进过编译,会在同步块的前后分别形成 monitorenter 和 monitorexit 这个两个字节码指令。在执行 monitorenter 指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计算器加 1,相应的,在执行 monitorexit 指令时会将锁计算器就减 1,当计算器为 0 时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被另一个线程释放为止。

    由于 ReentrantLock 是 java.util.concurrent 包下提供的一套互斥锁,相比 Synchronized,ReentrantLock 类提供了一些高级功能,主要有以下 3 项:

    (1)等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于 Synchronized 来说可以避免出现死锁的情况。

    (2)公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized 锁非公平锁,ReentrantLock 默认的构造函数是创建的非公平锁,可以通过参数 true 设为公平锁,但公平锁表现的性能不是很好。

    (3)锁绑定多个条件,一个 ReentrantLock 对象可以同时绑定对个对象。

    二、MySQL

    在这里插入图片描述

    1.Mysql 中有哪几种锁?

    (1)表级锁:开销小,加锁快。不会出现死锁,锁定粒度大,发生锁冲突的概率高,并发度低。

    (2)行级锁:开销大,加锁慢。会出现死锁,锁定粒度小,发生锁冲突的概率低,并发度高。

    (3)页面锁:开销时间、加锁时间、锁定粒度在 表级锁 与 行级锁 之间,会出现死锁,并发度中等。

    2.CHAR 与 VARCHAR 的区别?

    (1)CHAR 长度不可变,范围 1~255。若存储长度未达到定义的长度,则以 空格 填充。存取速度快,但容易浪费空间。

    (2)VARCHAR 长度可变,范围 1~65535。若存储长度未达到定义的长度,则存实际长度数据。存取速度稍慢,但节约空间。

    3.能说下myisam 和 innodb的区别吗?

    myisam引擎是5.1版本之前的默认引擎,支持全文检索、压缩、空间函数等,但是不支持事务和行级锁,所以一般用于有大量查询少量插入的场景来使用,而且myisam不支持外键,并且索引和数据是分开存储的。

    innodb是基于聚簇索引建立的,和myisam相反它支持事务、外键,并且通过MVCC来支持高并发,索引和数据存储在一起。

    4.你能说下事务的基本特性和隔离级别吗?
    事务:数据库中,对数据的一系列操作可以看成一个整体,称为事务。这个整体要么全部执行、要么全部不执行。
    ACID 属性的存在确保了 事务的可靠。
    (1)Actomicity(原子性):原子性要求 事务中的操作要么全部完成,要么回退成之前未操作的状态。即事务中某个操作失败后,会相当于什么都没发生,不会出现改了部分数据的情况。

    (2)Consistency(一致性):一致性要求 事务执行前后,数据库的状态一致,即从一个一致性状态切换到另一个一致性的状态。

    (3)Isolation(隔离性):隔离性要求 并发的事务相互隔离、不可见。即一个事务看不见另一个事务内部的操作以及操作的数据。

    (4)Durability(持久性):持久性要求 事务对数据库数据的修改是永久的。即数据一旦修改提交后,其状态将永久不变。

    5.并发问题 – 脏读、不可重复读、幻读?

    对于同时运行的多个事务,若这些事务访问同一数据时,没有采用必要的隔离机制,则会造成如下的并发问题。

    (1)脏读:脏读 指的是当一个事务正在访问某数据,并对这个数据进行的修改,且这条数据还未提交到数据库中,此时若另一个事务也访问到这条数据,获取到的是这条被修改的数据,此时得到的数据不对,即脏读。
    比如:tom 年龄为 22,事务 A 修改 tom 年龄为 30,此时还未提交到数据库,此时事务 B 获取 tom 年龄,得到的是 30,事务 A 回滚数据,数据库的数据依旧是 22,但事务 B 拿到的数据是 30,这就是脏读,读错了数据。

    (2)不可重复读:指一个事务,多次读取同一条数据,在这个事务还未结束时,另一个事务也访问该数据并对其修改,那么可能造成事务多次读取的数据不一致,即不可重复读。
    比如:tom 年龄为 22,事务 A 读取 tom 年龄为 22,事务未结束。此时事务 B 修改 tom 年龄为 30,并提交到数据库,当事务 A 再次读取 tom 年龄为 30,事务 A 两次读取的数据不一致,即不可重复读。

    (3)幻读:指事务并不是独立执行时产生的现象。一个事务修改某个表,涉及表的所有行,同时另一个事务也修改表,比如增加或删除一条数据。此时第一个事务发现多出或者少了一条数据。这种情况就是幻读。
    比如:事务 A 查询当前表的数据总数为 11, 此时事务 B 向表中插入一条数据,事务 A 再次查询当前表数据总数为 12,即幻读。

    注:
        不可重复读、幻读理解起来有些类似。
        不可重复读是对一条数据操作,重点在于修改某条数据。
        幻读是对表进行操作,重点在于新增或删除某条数据。

    6.事务的隔离级别?

    数据库系统必须具有隔离并发运行的事务的能力,使各事务间不会相互影响,避免并发问题。

    隔离级别:指的是一个事务与其他事务的隔离程度。隔离级别越高,则并发能力越弱。
    (1)Read Uncommitted(读未提交):即读取到 未提交的内容。
      一般不使用。此隔离级别下,查询不会加锁,即可能存在两个事务操作同一个表的情况。可能会导致 “脏读”、“不可重复读”、“幻读”。

    (2)Read Committed(读提交):即只能读取到 已提交的内容。
      常用(oracle、SQL Server 默认隔离级别)。此隔离级别下,查询采用 快照读 的机制,即不会读取到未提交的数据,从而避免 “脏读”,但是仍可能导致 “不可重复读”、“幻读”。

    (3)Repeatable Read(可重复读)
      常用(mysql 默认隔离级别)。此隔离级别下,查询采用 快照读 的机制,且事务启动后,当前数据不能被修改,从而可以避免 “不可重复读”,但是仍可能导致 “幻读”(新增或删除某条数据)。

    (4)Serializable(串行化)
      一般不使用。此隔离级别下,事务会串行化执行(排队执行),执行效率差、开销大。可以避免 “脏读”、“不可重复读”、“幻读“。

    【举例:】
    select @@transaction_isolation; -- 用于查看当前数据库的隔离级别(8.0版本)
     set session transaction isolation level read committed; --用于设置隔离级别为 read committed
    
    

    7.说说自增主键、UUID?

    (1)自增主键,数据在物理结构上是顺序存储,性能好,占用空间小。可以是 int 和 bigint 类型。int 4字节,bigint 8 字节,项目中理论不应出现 自增主键达到最大值的情况,因为数据太大,效率会大大降低,当出现一定的数据量后,应进行分库分表操作。

    (2)UUID,数据在物理结构上是随机存储,性能较差,占用空间大。唯一ID,绝不冲突。

    三、Spring

    在这里插入图片描述

    1. 什么是 Spring IOC 容器?

    Spring 框架的核心是 Spring 容器。容器创建对象,将它们装配在一起,配置它们并管理它们的完整生命周期。Spring 容器使用依赖注入来管理组成应用程序的组件。容器通过读取提供的配置元数据来接收对象进行实例化,配置和组装的指令。该元数据可以通过 XML,Java 注解或 Java 代码提供。

    在这里插入图片描述

    2. 什么是依赖注入?

    在依赖注入中,您不必创建对象,但必须描述如何创建它们。您不是直接在代码中将组件和服务连接在一起,而是描述配置文件中哪些组件需要哪些服务。由 IoC 容器将它们装配在一起。

    3. 可以通过多少种方式完成依赖注入?

    通常,依赖注入可以通过三种方式完成,即:

    构造函数注入
    setter 注入
    接口注入
    在 Spring Framework 中,仅使用构造函数和 setter 注入。

    4. 区分构造函数注入和 setter 注入。

    在这里插入图片描述

    5. spring 中有多少种 IOC 容器?

    BeanFactory - BeanFactory 就像一个包含 bean 集合的工厂类。它会在客户端要求时实例化 bean。

    ApplicationContext - ApplicationContext 接口扩展了 BeanFactory 接口。它在 BeanFactory 基础上提供了一些额外的功能。

    6. 区分 BeanFactory 和 ApplicationContext。

    在这里插入图片描述

    8. 什么是 spring bean?

    它们是构成用户应用程序主干的对象。
    Bean 由 Spring IoC 容器管理。
    它们由 Spring IoC 容器实例化,配置,装配和管理。
    Bean 是基于用户提供给容器的配置元数据创建。
    9. spring 提供了哪些配置方式?

    基于 xml 配置

    bean 所需的依赖项和服务在 XML 格式的配置文件中指定。这些配置文件通常包含许多 bean 定义和特定于应用程序的配置选项。它们通常以 bean 标签开头。例如:

    <bean id="studentbean" class="org.edureka.firstSpring.StudentBean">
            <property name="name" value="Edureka"></property>
        </bean>
    

    基于注解配置

    您可以通过在相关的类,方法或字段声明上使用注解,将 bean 配置为组件类本身,而不是使用 XML 来描述 bean 装配。默认情况下,Spring 容器中未打开注解装配。因此,您需要在使用它之前在 Spring 配置文件中启用它。例如:

    <beans>
            <context:annotation-config/>
            <!-- bean definitions go here -->
        </beans>
    

    基于 Java API 配置

    Spring 的 Java 配置是通过使用@Bean 和 @Configuration 来实现。

    2.1.@Bean 注解扮演与 元素相同的角色。

    2.2. @Configuration 类允许通过简单地调用同一个类中的其他@Bean 方法来定义 bean 间依赖关系。

    例如:

    public class StudentConfig {
        @Bean
        public StudentBean myStudent() {
            return new StudentBean();
        }
    }
    
    

    10.什么是 AOP?

    AOP(Aspect-Oriented Programming), 即 面向切面编程, 它与 OOP( Object-Oriented Programming, 面向对象编程) 相辅相成, 提供了与 OOP 不同的抽象软件结构的视角. 在 OOP 中, 我们以类(class)作为我们的基本单元, 而 AOP 中的基本单元是 Aspect(切面)

    11. AOP 中的 Aspect、Advice、Pointcut、JointPoint 和 Advice 参数分别是什么?


    在这里插入图片描述
    • Aspect - Aspect 是一个实现交叉问题的类,例如事务管理。方面可以是配置的普通类,然后在 Spring Bean配置文件中配置,或者我们可以使用 Spring AspectJ 支持使用 @Aspect 注解将类声明为 Aspect。
    • Advice - Advice 是针对特定 JoinPoint 采取的操作。在编程方面,它们是在应用程序中达到具有匹配切入点的特定JoinPoint 时执行的方法。您可以将 Advice 视为 Spring 拦截器(Interceptor)或 Servlet过滤器(filter)。
    • Advice Arguments - 我们可以在 advice 方法中传递参数。我们可以在切入点中使用 args()表达式来应用于与参数模式匹配的任何方法。如果我们使用它,那么我们需要在确定参数类型的 advice 方法中使用相同的名称。
    • Pointcut - Pointcut 是与 JoinPoint 匹配的正则表达式,用于确定是否需要执行 Advice。 Pointcut使用与 JoinPoint 匹配的不同类型的表达式。Spring 框架使用 AspectJ Pointcut表达式语言来确定将应用通知方法的 JoinPoint。
    • JoinPoint - JoinPoint 是应用程序中的特定点,例如方法执行,异常处理,更改对象变量值等。在 Spring AOP中,JoinPoint 始终是方法的执行器。

    12. AOP 有哪些实现方式?

    实现 AOP 的技术,主要分为两大类:

    静态代理 - 指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;编译时编织(特殊编译器实现)类加载时编织(特殊的类加载器实现)。
    动态代理 - 在运行时在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。 JDK 动态代理CGLIB

    13. Spring AOP and AspectJ AOP 有什么区别?

    Spring AOP 基于动态代理方式实现;AspectJ 基于静态代理方式实现。 Spring AOP 仅支持方法级别的 PointCut;提供了完全的 AOP 支持,它还支持属性级别的 PointCut。

    五、最后:

    针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。

    下面的面试题答案都整理成文档笔记。也还整理了一些面试资料&最新2020收集的一些大厂的面试真题(都整理成文档,小部分截图),需要的朋友关注公众号【Java斗帝】回复“666”免费获取。

    image

    看完三件事❤️

    如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:

    点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。

    关注公众号 『 Java斗帝 』,不定期分享原创知识。

    同时可以期待后续文章ing🚀

    相关文章

      网友评论

        本文标题:Java程序员必会的三个技能:Spring+MySQL+并发编程

        本文链接:https://www.haomeiwen.com/subject/eausvktx.html