• 面试总结2


    用时:40min

    • 自我介绍
    • hashset存储对象怎么进行判断是否重复
    • Synchronized底层实现原理

    • Synchronized锁的升级过程

    • 讲讲开发中常用到的spring注解
    • spring事务,传播机制,隔离级别
    • 说一说你常用的git命令
    • 说一说你对IOC、AOP的理解

    1、hashset存储对象怎么进行判断是否重复

    HashSet  根据每个对象的哈希码值(调用hashCode()获得)用固定的算法算出它的存储索引,把对象存放在一个叫散列表的相应位置(表元)中:

    • 存对象时,hashSet集合首先调用该对象的hashCode方法来获得该对象的hashCode值,与hash表中的值进行比较。如果不存在,则直接把该对象存入集合中,并把该hashCode值存入hash表中,此次add操作结束。如果存在,则进行下面的计算。

    • 通过”==”操作符判断已经存入的对象与要存入的对象是否为同一对象。如果true则集合认为添加相同对象,add失败。如果false(不是同一对象)则进行下面的计算。(这一条很重要,保证了,不管如何操作,在HashSet中都不可能存入同一个对象两次)

    • 调用要添加的对象的equals()方法,并把集合中的另一元素作为参数传入,如果返回值为true则集合认为添加相同对象,add失败。否则添加成功。

    2、Synchronized底层实现原理

    同步方法通过ACC_SYNCHRONIZED 关键字隐式的对方法进行加锁。当线程要执行的方法被标注上ACC_SYNCHRONIZED时,需要先获得锁才能执行该方法。
    同步代码块通过monitorenter和monitorexit执行来进行加锁。当线程执行到monitorenter的时候要先获得锁,才能执行后面的方法。当线程执行到monitorexit的时候则要释放锁。每个对象自身维护着一个被加锁次数的计数器,当计数器不为0时,只有获得锁的线程才能再次获得锁。 

    3、Synchronized锁的升级过程

    Java SE 1.6 为了减少获得锁和释放锁带来的性能消耗,引入了 “偏向锁” 和 “轻量级锁”:锁一共有 4 种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。锁可以升级但不能降级。

    偏向锁:大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头和栈帧中记录存储锁偏向的线程ID,以后该线程在进入同步块时先判断对象头的Mark Word里是否存储着指向当前线程的偏向锁,如果存在就直接获取锁。

    轻量级锁:当其他线程尝试竞争偏向锁时,锁升级为轻量级锁。线程在执行同步块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头中的MarkWord替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,标识其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

    重量级锁:锁在原地循环等待的时候,是会消耗CPU资源的。所以自旋必须要有一定的条件控制,否则如果一个线程执行同步代码块的时间很长,那么等待锁的线程会不断的循环反而会消耗CPU资源。默认情况下锁自旋的次数是10 次,可以使用-XX:PreBlockSpin参数来设置自旋锁等待的次数。10次后如果还没获取锁,则升级为重量级锁。

    补充:synchronized锁表现三种形势

    Java中每个对象都可以作为锁。具体表现为以下3种方式:

    1. 对于普通方法,锁的是当前实例对象
    1. public class SynchronizedTest {
    2. public synchronized void test(String name){
    3. System.out.println(name+"开始执行");
    4. try {
    5. Thread.sleep(2000);
    6. }catch (Exception e){
    7. }
    8. System.out.println(name+"执行完毕");
    9. }
    10. public static void main(String[] args) throws Exception {
    11. SynchronizedTest t=new SynchronizedTest();
    12. new Thread(new Runnable() {
    13. @Override
    14. public void run() {
    15. t.test("线程1");
    16. }
    17. }).start();
    18. SynchronizedTest t1=new SynchronizedTest();
    19. new Thread(new Runnable() {
    20. @Override
    21. public void run() {
    22. t1.test("线程2");
    23. }
    24. }).start();
    25. }
    26. }

     上面这个代码我们new了两个不同的对象。打印结果如下。线程2并没有等线程1执行完成后才执行,说明对于普通方法,如果是不同的对象实例锁是不起作用的

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

    我们把上面的代码修改一下,改为同一个实例

    1. public class SynchronizedTest {
    2. public synchronized void test(String name){
    3. System.out.println(name+"开始执行");
    4. try {
    5. Thread.sleep(2000);
    6. }catch (Exception e){
    7. }
    8. System.out.println(name+"执行完毕");
    9. }
    10. public static void main(String[] args) throws Exception {
    11. SynchronizedTest t=new SynchronizedTest();
    12. new Thread(new Runnable() {
    13. @Override
    14. public void run() {
    15. t.test("线程1");
    16. }
    17. }).start();
    18. new Thread(new Runnable() {
    19. @Override
    20. public void run() {
    21. t.test("线程2");
    22. }
    23. }).start();
    24. }
    25. }

     打印结果如下。从打印结果可以看出同一个对象实例的时候,第二个线程只有等到第一个线程执行完成后才开始执行。

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

      2.对于静态同步方法,锁的是当前类的Class对象。

    1. public class SynchronizedTest {
    2. public synchronized static void test(String name){
    3. System.out.println(name+"开始执行");
    4. try {
    5. Thread.sleep(2000);
    6. }catch (Exception e){
    7. }
    8. System.out.println(name+"执行完毕");
    9. }
    10. public static void main(String[] args) throws Exception {
    11. new Thread(new Runnable() {
    12. @Override
    13. public void run() {
    14. SynchronizedTest.test("线程1");
    15. }
    16. }).start();
    17. new Thread(new Runnable() {
    18. @Override
    19. public void run() {
    20. SynchronizedTest.test("线程2");
    21. }
    22. }).start();
    23. }
    24. }

     打印结果如下。第一个线程执行完成后才开始执行第二个线程。

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

      3.对于同步方法快,锁的是synchonized括号里配置的对象。

    1. public class SynchronizedTest {
    2. public void test(String name){
    3. Object o=new Object();
    4. synchronized(o.getClass()){
    5. System.out.println(name+"开始执行");
    6. try {
    7. Thread.sleep(2000);
    8. }catch (InterruptedException e){
    9. e.printStackTrace();
    10. }
    11. System.out.println(name+"执行完毕");
    12. }
    13. }
    14. public static void main(String[] args) throws Exception {
    15. SynchronizedTest t=new SynchronizedTest();
    16. new Thread(new Runnable() {
    17. @Override
    18. public void run() {
    19. t.test("线程1");
    20. }
    21. }).start();
    22. SynchronizedTest t1=new SynchronizedTest();
    23. new Thread(new Runnable() {
    24. @Override
    25. public void run() {
    26. t1.test("线程2");
    27. }
    28. }).start();
    29. }
    30. }

    打印结果如下,第一个线程执行完成后才开始执行第二个线程。

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

    4、spring常用注解

     

     

     

     

    5、spring事务的传播机制和隔离级别

    spring事务的传播级别:


    PROPAGATION_REQUIRED:Spring的默认传播级别,如果上下文中存在事务则加入当前事务,如果不存在事务则新建事务执行。
    PROPAGATION_SUPPORTS:如果上下文中存在事务则加入当前事务,如果没有事务则以非事务方式执行。
    PROPAGATION_MANDATORY:该传播级别要求上下文中必须存在事务,否则抛出异常。
    PROPAGATION_REQUIRES_NEW:该传播级别每次执行都会创建新事务,并同时将上下文中的事务挂起,执行完当前线程后再恢复上下文中事务。(子事务的执行结果不影响父事务的执行和回滚)
    PROPAGATION_NOT_SUPPORTED:当上下文中有事务则挂起当前事务,执行完当前逻辑后再恢复上下文事务。(降低事务大小,将非核心的执行逻辑包裹执行。)
    PROPAGATION_NEVER:该传播级别要求上下文中不能存在事务,否则抛出异常。
    PROPAGATION_NESTED:嵌套事务,如果上下文中存在事务则嵌套执行,如果不存在则新建事务。(save point概念)

    事务隔离级别:
     

    脏读:读取到了别的事务回滚前的数据,例如B事务修改数据库X,在未提交前A事务读取了X的值,而B事务发生了回滚。
    不可重复读:一个事务在两次读取同一个数据的值不一致。例如A事务读取X,在中间过程中B事务修改了X的值,事务A再次读取X时值发生了改变。
    幻读:查询得到的数据条数发生了改变,例如A事务搜索数据时有10条数据,在这时B事务插入了一条数据,A事务再搜索时发现数据有11条了。
    数据隔离级别
    read-uncommitted:未提交读(脏读、不可重复读、幻读)
    read-committed:已提交读(不可重复读、幻读),大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。
    repeatable-read:可重复读(幻读),保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。
    serializable:串行化最严格的级别,事务串行执行,资源消耗最大

    Spring事务传播和隔离级别配置


    @Transactional(propagation=Propagation.REQUIRED,rollbackFor=Exception.class,timeout=1,isolation=Isolation.DEFAULT)

    事务的传播性:@Transactional(propagation=Propagation.REQUIRED) 如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
    事务的超时性:@Transactional(timeout=30) //默认是30秒
    事务的隔离级别:@Transactional(isolation = Isolation.READ_UNCOMMITTED)
    回滚指定异常类:@Transactional(rollbackFor={RuntimeException.class,
    Exception.class})
    只读:@Transactional(readOnly=true)该属性用于设置当前事务是否为只读事务,设置为true表示只读

    6、常用的git命令

    远程仓库中clone代码到本地:git clone https://github.com/MatchlessHeroVIP/ssmtest.git

    新增文件的命令:git add file或者git add .

    提交文件的命令:git commit –m或者git commit –a

    本地仓库提交到远程仓库:git push

    查看工作区状况:git status –s

    拉取合并远程分支的操作:git fetch/git merge或者git pull

    查看提交记录命令:git reflog

    切换到主分支: git checkout master


     

     7、说一说你对IOC、AOP的理解

    具体的看前面的IOC、AOP笔记即可

     

  • 相关阅读:
    【每日一题】ABC194E-Mex Min | 思维 | 树状数组二分 | 中等
    药智网数据库介绍
    【ArcGIS微课1000例】0031:ArcGIS中的32个拓扑规则(图文详解)
    ES 关于text和keyword两种类型数据搜索区别
    python ToastNotifier TypeError got Nonetype
    Rust泛型与trait特性,模仿接口的实现
    Python学习记录——이십이 Bytes和字符集编码
    完完整整地看完这个故事,你敢说还不懂Docker?
    关于mac下pycharm旧版本没删除的情况下新版本2023安装之后闪退
    Linux学习记录——이십팔 网络基础(1)
  • 原文地址:https://blog.csdn.net/lwj_07/article/details/128145836