java面试篇 4.9(mybatis+微服务+线程安全+线程池)

发布于:2025-04-19 ⋅ 阅读:(23) ⋅ 点赞:(0)

目录

mybatis:

1、mybatis的执行流程

2、mybatis是否支持延迟加载?

当我们需要去开启全局的懒加载时:

3、mybatis的一级和二级缓存

微服务

1、springcloud五大组件有哪些

2、服务注册和发现是什么意思?springcloud如何实现服务注册发现的?

3、负载均衡时如何实现的?

ribbon的负载均衡策略有哪些?

​编辑

 4、什么是服务雪崩,怎么解决这个问题?

5、你们的微服务是怎么监控的?

6、微服务限流

7、分布式系统理论

 8、分布式事务的解决方案

线程安全

1、synchronized关键字的底层原理

2、synchronized底层原理进阶

3、你谈谈JMM(java的内存模型)

4、CAS你知道吗

线程池

1、线程池的核心参数和执行原理?​编辑​编辑

2、线程池中有哪些常见的阻塞队列?

3、如何确定核心线程数?

4、线程池的种类有哪些?(executors不建议用)

5、为什么不建议使用executors创建线程池?

6、线程池的使用场景?

1、countDownLatch 

​编辑

2、数据汇总

3、异步调用

7、如何控制某个方法允许并发访问线程的数量?

一些其他问题:

1.Springboot如何解决跨域问题?

方法一:使用@CrossOrigin注解

1. 作用于方法

2. 作用于类

方法二:配置WebMvcConfigurer实现跨域

说明

方法三:使用CorsFilter实现跨域

示例代码

方法四:使用CorsConfigurationSource实现跨域

示例代码

方法选择建议

2、mybatis的二级缓存的Serializable接口怎么实现?

实体类实现 Serializable 接口

3、线程的原子性指的是什么

原子性的关键点

常见的原子操作

如何保证原子性

原子性的重要性

总结


mybatis:

1、mybatis的执行流程

1、读取mybatis-config.xml文件确定连接的是什么数据库,扫描的是哪些Mapper文件。

2、创建一个sqlsessionFactory会话工厂,它可以创建多个sqlsession会话,包含了执行SQL语句的方法

3、执行器是真正操作数据库接口的,并且维护一些缓存

4、MapperedStatement对象会封装某些信息,比如Mapper映射文件的名称、映射文件的哪个方法、执行的SQL语句、数据库返回的结果

5、将java传来的参数类型转化为数据库支持的数据类型。执行完后要转为java类型。

2、mybatis是否支持延迟加载?

立即查询: 

延迟加载:

当我们不需要获取订单信息,系统不会执行查询订单信息的SQL语句。

当我们需要去开启全局的懒加载时:

<setting name="lazyLoadingEnabled" value ="true"/>

原理:创建一个代理对象

3、mybatis的一级和二级缓存

作用于同一个session的意思就是,通过同一个session执行的方法。

添加<cache/>标签。 

两个查询语句只会执行一个SQL语句(同一个Mapper文件的方法):

3的意思是第一个sqlsession结束才能将一级缓存转移到二级缓存。 

微服务

1、springcloud五大组件有哪些

配置中心写在springcloud.config文件中。

zuul被淘汰了。 

2、服务注册和发现是什么意思?springcloud如何实现服务注册发现的?

 ap是高可用模式,cp是强一致模式。

3、负载均衡时如何实现的?

 

ribbon的负载均衡策略有哪些?

 4、什么是服务雪崩,怎么解决这个问题?

一个服务的所有连接数是确定的,当服务A向服务D发送请求失败时,连接并不会被释放,A持续向D发送请求会占满A的最大连接数,故而A无法再提供服务,A发生了宕机,而这是一个链式的崩溃过程。

解决方法:

服务降级

服务熔断 

服务降级是针对接口,当服务的某个方法不可用时,我们对该接口的方法进行降级,它会直接返回错误而不占用连接,而仅仅是部分方法不可用不需要关闭整个服务,当请求失败率达到50%以上则触发熔断,整个服务会被关闭,进入熔断机制。熔断针对的是整个服务不可用。

5、你们的微服务是怎么监控的?

解决如下四个问题。

6、微服务限流

漏桶算法指的就是以固定速率漏出请求,超出桶容限的请求等待或抛弃。漏桶的请求数量更加平滑。

漏桶它的请求数是可以波动的,因为令牌数量可以累计,如果某一时间请求数很大会拿走大量的令牌。默认使用Redis存储令牌桶,所以需要在网关的配置文件中配置Redis连接。

7、分布式系统理论

要么是cp要么是ap

 8、分布式事务的解决方案

线程安全

1、synchronized关键字的底层原理

汇编执行流程:

2、synchronized底层原理进阶

对象的对象头中存有Markword,Markword会存有monitor的地址,从而关联对象锁和monitor。 

重量级锁:

轻量级锁的执行流程:

1、创建一个线程以后,线程里面会生成一个lock Record,里面的Object reference会指向对象锁,lock recode地址00存储锁地址然后与对象锁的marokword进行内容交换,那么对象锁的markword就会从无锁的状态转为轻量级锁(如果多个线程竞争一个对象锁,会升级为重量级锁),而tread-0中就会存储对象锁的原本内容,比如hashcode这些。

2、如果线程1执行中调用了线程2,则称之为锁重录,锁重录不是两个线程竞争对象锁,它仍然是一个线程,所以用轻量级锁即可。

3、发生锁重录后,线程Thread-0会再创建一个lock record,里面的lockrecord地址00会再与对象锁发生CAS连接,但是不发生交换,里面的内容为Null。

4、当线程逐渐执行完时,为lock record地址00为null的先执行完并被删除,CAS不会发生交换。而最后不为null的Lock Record会先与对象锁发生CAS交换,Markword部分再次变为无锁状态。然后LockRecord被删除,线程执行完。

偏向锁:

3、你谈谈JMM(java的内存模型)

4、CAS你知道吗

自旋的意思就是重新拉一份旧数据进行执行,保证线程的原子性。 

线程池

1、线程池的核心参数和执行原理?

具体实现:

2、线程池中有哪些常见的阻塞队列?

3、如何确定核心线程数?

开发时一般都是io密集型任务。

4、线程池的种类有哪些?(executors不建议用)

具体实现:会提供一个延迟执行的效果。

5、为什么不建议使用executors创建线程池?

6、线程池的使用场景?

1、countDownLatch 

具体实现:

真实案例:

1、设置好countDownLatch(总页数),每一个线程完成一个页面数据的导入,只有循环执行完总页数大小的线程数量才能导入完所有的数据。

2、每一页数据导入到es的任务是提交给线程池执行。

2、数据汇总

具体实现:

串行执行:

并行执行:

3、异步调用

搜索记录的保存。

@ASync注解

7、如何控制某个方法允许并发访问线程的数量?

控制一个方法内最多使用的线程数量。

一些其他问题:

1.Springboot如何解决跨域问题?

方法一:使用@CrossOrigin注解

@CrossOrigin注解是Spring框架提供的一个非常便捷的方式来解决跨域问题,它可以直接作用于Controller类或方法上。

1. 作用于方法

在具体的Controller方法上添加@CrossOrigin注解,仅对该方法生效。

@RestController
@RequestMapping("/api")
public class MyController {
    @GetMapping("/test")
    @CrossOrigin
    public String test() {
        return "Hello, CrossOrigin!";
    }
}

这样,只有/api/test接口允许跨域请求。

2. 作用于类

@CrossOrigin注解添加到Controller类上,该类中的所有方法都将允许跨域。

@RestController
@RequestMapping("/api")
@CrossOrigin
public class MyController {
    @GetMapping("/test1")
    public String test1() {
        return "Hello, CrossOrigin!";
    }

    @GetMapping("/test2")
    public String test2() {
        return "Hello, CrossOrigin!";
    }
}

这种方式适合整个Controller类中的所有接口都需要跨域的情况。

方法二:配置WebMvcConfigurer实现跨域

通过实现WebMvcConfigurer接口并重写addCorsMappings方法,可以全局配置跨域规则。

@Configuration
public class GlobalCorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 配置跨域规则
        registry.addMapping("/**") // 允许跨域的路径
                .allowedOrigins("*") // 允许跨域的来源,*表示所有
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的请求方法
                .allowedHeaders("*") // 允许的请求头
                .allowCredentials(true) // 是否允许发送Cookie
                .maxAge(3600); // 预检请求的有效期
    }
}
说明
  • allowedOrigins:允许哪些来源进行跨域请求,*表示允许所有来源。

  • allowedMethods:允许哪些HTTP方法进行跨域请求,如GETPOST等。

  • allowedHeaders:允许哪些请求头。

  • allowCredentials:是否允许发送Cookie和HTTP认证信息。如果设置为true,则allowedOrigins不能为*,必须指定具体来源。

  • maxAge:预检请求的有效期,单位为秒。

方法三:使用CorsFilter实现跨域

通过自定义CorsFilter来实现跨域,这种方式更加灵活,可以对请求进行更细粒度的控制。

示例代码
@Component
public class MyCorsFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest request = (HttpServletRequest) req;

        // 设置允许的来源
        response.setHeader("Access-Control-Allow-Origin", "*");
        // 设置允许的请求头
        response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, remember-me");
        // 设置允许的请求方法
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        // 设置是否允许发送Cookie
        response.setHeader("Access-Control-Allow-Credentials", "true");
        // 设置预检请求的有效期
        response.setHeader("Access-Control-Max-Age", "3600");

        // 如果是OPTIONS请求,直接返回
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            chain.doFilter(req, res);
        }
    }

    @Override
    public void init(FilterConfig filterConfig) {
    }

    @Override
    public void destroy() {
    }
}

方法四:使用CorsConfigurationSource实现跨域

通过实现CorsConfigurationSource接口,可以动态地为不同的请求路径配置不同的跨域规则。

示例代码
@Configuration
public class CorsConfig {
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*")); // 允许的来源
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); // 允许的请求方法
        configuration.setAllowedHeaders(Arrays.asList("*")); // 允许的请求头
        configuration.setAllowCredentials(true); // 是否允许发送Cookie
        configuration.setMaxAge(3600L); // 预检请求的有效期

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration); // 配置路径
        return source;
    }
}

方法选择建议

  • 如果只需要为个别接口或Controller类解决跨域问题,可以使用@CrossOrigin注解。

  • 如果需要全局配置跨域规则,推荐使用WebMvcConfigurerCorsConfigurationSource

  • 如果需要更细粒度的控制,例如动态调整跨域规则,可以使用CorsFilterCorsConfigurationSource

根据实际需求选择合适的方法即可。

2、mybatis的二级缓存的Serializable接口怎么实现?

实体类实现 Serializable 接口

首先,定义一个实体类并实现 Serializable 接口。例如,定义一个 User 类:

import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID = 1L;//看来最主要的就是这句话

    private Long id;
    private String name;
    private String email;

    // Getters and Setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}

 二级缓存配置参数说明
eviction:指定缓存回收策略,可选值有:
FIFO(先进先出)
LRU(最近最少使用)
SOFT(软引用)
WEAK(弱引用)
flushInterval:缓存刷新间隔时间(毫秒),默认不刷新。
size:缓存的最大对象数。
readOnly:是否只读。如果设置为 true,则缓存的对象是只读的,不能被修改;如果设置为 false,则缓存的对象可以被修改。

3、线程的原子性指的是什么

线程的原子性(Atomicity)是指在多线程环境中,一个操作或一系列操作要么全部完成,要么全部不完成,不会出现部分完成的情况。换句话说,原子操作在执行过程中不会被其他线程中断。

原子性的关键点

  1. 不可分割性

    • 原子操作是不可分割的,不会在操作的中间被其他线程打断。

    • 例如,一个线程正在执行某个原子操作时,其他线程无法插入执行,必须等待该操作完成。

  2. 线程安全性

    • 原子操作通常用于解决多线程环境下的线程安全问题。

    • 如果多个线程同时访问和修改共享资源,原子操作可以确保操作的完整性,避免数据不一致。

常见的原子操作

  1. 单个变量的读写操作

    • 对于基本数据类型(如 intlongdouble 等),单个变量的读写操作通常是原子的。

    • 例如,x = 10 是一个原子操作。

  2. 复合操作

    • 一些复合操作(如 x++x += 10)在多线程环境下可能不是原子的。

    • 例如,x++ 实际上包括三个步骤:读取 x 的值、加 1、写回 x 的值。如果多个线程同时执行 x++,可能会导致数据竞争(Race Condition)。

如何保证原子性

  1. 使用同步机制

    • synchronized 关键字:在 Java 中,synchronized 可以确保多个线程不会同时进入同步代码块。

      java

      复制

      public class Counter {
          private int count = 0;
      
          public synchronized void increment() {
              count++;
          }
      
          public synchronized int getCount() {
              return count;
          }
      }
    • ReentrantLockReentrantLock 提供了比 synchronized 更灵活的锁机制。

      java

      复制

      import java.util.concurrent.locks.Lock;
      import java.util.concurrent.locks.ReentrantLock;
      
      public class Counter {
          private final Lock lock = new ReentrantLock();
          private int count = 0;
      
          public void increment() {
              lock.lock();
              try {
                  count++;
              } finally {
                  lock.unlock();
              }
          }
      
          public int getCount() {
              lock.lock();
              try {
                  return count;
              } finally {
                  lock.unlock();
              }
          }
      }
  2. 使用原子类

    • Java 提供了 java.util.concurrent.atomic 包,其中包含一系列原子类(如 AtomicIntegerAtomicLongAtomicReference 等)。

      java

      复制

      import java.util.concurrent.atomic.AtomicInteger;
      
      public class Counter {
          private final AtomicInteger count = new AtomicInteger(0);
      
          public void increment() {
              count.incrementAndGet();
          }
      
          public int getCount() {
              return count.get();
          }
      }
  3. 使用 volatile 关键字

    • volatile 修饰的变量可以保证可见性,但不能保证复合操作的原子性。

    • 例如,volatile 可以确保多个线程看到变量的最新值,但 x++ 仍然不是原子操作。

      java

      复制

      public class Counter {
          private volatile int count = 0;
      
          public void increment() {
              count++; // 不是原子操作
          }
      
          public int getCount() {
              return count;
          }
      }

原子性的重要性

  • 避免数据竞争:在多线程环境下,原子性可以避免多个线程同时修改共享资源导致的数据不一致问题。

  • 保证线程安全:通过原子操作,可以确保程序的正确性和稳定性。

总结

原子性是多线程编程中的一个重要概念,它确保操作的不可分割性和完整性。通过使用同步机制(如 synchronizedReentrantLock)、原子类(如 AtomicInteger)或 volatile 关键字,可以实现原子操作,从而保证线程安全。


网站公告

今日签到

点亮在社区的每一天
去签到