Java-面试

2021-11-25

1 线程安全方面的集合?

Vector,Vector性能最差,所有的方法都是加了synchronized来同步,从而保证线程安全
SynchronizedList是Collections类的静态内部类,它能把所有容器集合转换成线程安全的,比 Vector 有更好的扩展性和兼容性。

CopyOnWriteArrayList是java1.5以后才加入的新类,从命名可以理解为复制在写入的List。

java.util.concurrent.ConcurrentHashMap 中的并发集合视图:keySet()、values() 和 entrySet() 方法返回的集合视图也是线程安全的。

它的添加时加锁的(ReentrantLock ,非synchronized同步锁),读操作是没有加锁。
java.util.concurrent.ConcurrentLinkedDeque:它是一个线程安全的无界双端队列实现,支持高效的并发操作。

java.util.concurrent.LinkedBlockingQueue:它是一个线程安全的可选有界或无界队列实现。它采用可选的容量限制来控制并发操作。

java.util.concurrent.BlockingQueue 接口的其他实现类:ArrayBlockingQueue、LinkedBlockingDeque、PriorityBlockingQueue 等都是线程安全的阻塞队列实现。

2 hashmap怎么转为线程安全?

场景:以前的业务实现就是一个hashmap,现在要保证线程安全如何实现?
HashMap 是非线程安全的集合类,如果在多线程环境下同时访问和修改 HashMap 实例,可能会导致不一致的结果或其他线程安全问题。

要将 HashMap 转换为线程安全的集合类,可以使用 java.util.Collections 类中的 synchronizedMap 方法。该方法返回一个线程安全的包装器,它可以确保对被包装的 HashMap 实例的访问和修改是同步的。

以下是将 HashMap 转换为线程安全的示例代码:

HashMap<String, Integer> hashMap = new HashMap<>();
// 添加元素到 HashMap

Map<String, Integer> synchronizedMap = Collections.synchronizedMap(hashMap);
// 使用 synchronizedMap 方法将 HashMap 转换为线程安全的 Map

// 在多线程环境下使用 synchronizedMap 进行访问和修改操作
// 注意:对 synchronizedMap 的迭代操作需要手动进行同步控制

需要注意的是,虽然 synchronizedMap 方法可以将 HashMap 转换为线程安全的集合类,但在高并发或高度竞争的环境中,性能可能会受到影响。在这种情况下,考虑使用 ConcurrentHashMap 或其他并发集合类来获得更好的并发性能。

3 请问用过ConcurrentHashMap源码吗?知道它用了哪些锁吗?

在JDK 1.8中,ConcurrentHashMap的实现与之前的版本略有不同。在JDK 1.8中,ConcurrentHashMap使用了以下两种类型的锁:

Synchronized锁:ConcurrentHashMap在JDK 1.8之前使用了分段锁,即每个段(Segment)都有一个独立的锁。但在JDK 1.8中,ConcurrentHashMap使用了更加细粒度的synchronized锁来实现并发控制。它将整个哈希表分为若干个桶(Bucket),每个桶内部使用synchronized锁来保证对该桶的并发访问的线程安全性。

CAS操作:在JDK 1.8中,ConcurrentHashMap在节点(Node)的插入、删除和更新操作中使用了CAS(Compare and Swap)操作,而不是使用锁。这样可以减少锁的竞争和开销,提高并发性能。

需要注意的是,JDK 1.8中的ConcurrentHashMap并没有完全放弃分段锁的思想,而是将分段锁转化为了细粒度的synchronized锁。这样可以在保证线程安全性的同时,提高并发度和性能。

4 CAS原理,CAS中的一些经典问题?ABA

CAS(Compare and Swap)自旋锁是一种乐观锁的实现方式,它通过不断自旋尝试更新共享变量的值来实现并发控制。CAS操作包含三个操作数:共享变量的内存地址、旧的预期值和新的值。CAS操作执行时,先比较内存中的值和旧的预期值是否相等,如果相等,则将内存中的值更新为新的值。如果不相等,则说明在操作期间共享变量的值被其他线程修改了,CAS操作失败,需要重新读取内存中的值重新尝试。

CAS自旋锁的工作原理如下:

线程尝试通过CAS操作来获取锁。它会比较锁的当前状态(通常是一个标志位)和期望的状态是否相等。
如果相等,说明锁是可用的,线程可以获取到锁,执行相应的操作。
如果不相等,说明有其他线程已经获取了锁,线程需要进行自旋等待,不断尝试CAS操作获取锁。
当前线程在自旋等待期间,不会进入阻塞状态,而是使用循环来自旋尝试获取锁。这样可以减少线程上下文切换的开销。
如果获取到锁,线程执行完操作后,将锁的状态重置为可用状态。
如果尝试获取锁的次数超过一定的阈值,为了避免线程长时间自旋浪费CPU资源,可以将线程挂起或者进行其他策略,如使用阻塞锁。
CAS自旋锁的优点是避免了线程切换的开销,对于短时间的竞争情况下性能较好。缺点是当线程间竞争激烈时,自旋等待可能会导致CPU资源的浪费。因此,对于长时间的竞争或者竞争较激烈的情况,使用其他类型的锁,如悲观锁(如Synchronized)可能更适合。

5 java怎么实现解决ABA问题?

答:AtomicStampedReference类,
Java中使用AtomicStampedReference来解决CAS中的ABA问题,它不再像compareAndSet方法
中只比较内存中的值也当前值是否相等,而且先比较引用是否相等,然后比较值是否相等,这样就避免了ABA问题。
解决 ABA 问题的原子类:AtomicMarkableReference(通过引入一个 boolean
来反映中间有没有变过),AtomicStampedReference(通过引入一个 int 来累
加来反映中间有没有变过)

ABA问题指的是在使用CAS操作时,可能会出现以下序列:一个线程读取了一个值A,另一个线程将该值改为B,然后又改回了A,最后第一个线程进行CAS操作时发现值还是A,误以为没有被修改过。这种情况下,CAS操作无法正确地检测到值的变化,可能会导致数据一致性问题。

为了解决ABA问题,可以采用以下两种方案:

版本号/标记位:在CAS操作中引入版本号或标记位。每次对共享变量的修改都会增加版本号或改变标记位,使得每次修改都是唯一的。当进行CAS操作时,除了比较共享变量的值外,还要比较版本号或标记位是否一致。这样可以防止ABA问题的发生。

借助AtomicStampedReference:AtomicStampedReference是Java并发包提供的一个原子类,它可以解决CAS操作的ABA问题。AtomicStampedReference包含了一个引用和一个版本号(或者称为时间戳)。它可以通过比较引用和版本号同时是否一致来判断共享变量是否发生了修改。使用AtomicStampedReference可以在CAS操作中避免ABA问题的发生。

需要注意的是,使用以上两种方案,都需要在编程时注意对共享变量的修改进行合理的版本控制或标记位的管理。这样可以保证CAS操作的正确性和数据一致性。

6你项目用redis主要做什么?redis应用场景

  1. 缓存:Redis被广泛用作缓存层,将热点数据存储在内存中,加速读写访问速度。它可以减轻后端数据库的负载,提高系统的整体性能。由于Redis的高性能和灵活性,它是许多网站和应用中常用的缓存解决方案。
  2. 会话存储:Redis可以用作会话存储,将用户的会话数据存储在内存中,提供快速的访问和高并发处理能力。它可以用于存储用户登录信息、购物车数据、用户偏好等。
  3. 消息队列:Redis的发布-订阅功能可以用来构建消息系统,实现消息的发布和订阅模式。它可以用于异步任务处理、事件驱动等场景。
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;

public class RedisMessageQueueExample {
    public static void main(String[] args) {
        // 创建生产者线程
        Thread producerThread = new Thread(() -> {
            Jedis jedis = new Jedis("localhost");

            // 发布消息到队列
            for (int i = 0; i < 10; i++) {
                jedis.rpush("my_queue", "Message " + i);
                System.out.println("Produced: Message " + i);
            }

            jedis.close();
        });

        // 创建消费者线程
        Thread consumerThread = new Thread(() -> {
            Jedis jedis = new Jedis("localhost");

            // 订阅消息队列
            jedis.subscribe(new JedisPubSub() {
                @Override
                public void onMessage(String channel, String message) {
                    System.out.println("Consumed: " + message);
                }
            }, "my_queue");

            jedis.close();
        });

        // 启动生产者和消费者线程
        producerThread.start();
        consumerThread.start();
    }
}

  1. 计数器和排行榜:Redis的原子性操作和排序功能非常适合实现计数器和排行榜功能。可以通过Redis的命令实现实时统计、热门排行等功能。
import redis.clients.jedis.Jedis;
import java.util.Map;
import java.util.Set;

public class RedisCounterLeaderboardExample {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost");

        // 计数器示例
        // 增加计数器
        jedis.incr("counter");
        // 获取当前计数器的值
        System.out.println("Counter: " + jedis.get("counter"));

        // 排行榜示例
        // 添加用户分数
        jedis.zadd("leaderboard", 100, "user1");
        jedis.zadd("leaderboard", 200, "user2");
        jedis.zadd("leaderboard", 300, "user3");
        // 获取排行榜前三名
        Set<String> leaderboard = jedis.zrevrange("leaderboard", 0, 2);
        int rank = 1;
        for (String user : leaderboard) {
            System.out.println("Rank " + rank + ": " + user);
            rank++;
        }

        jedis.close();
    }
}

  1. 分布式锁:Redis的原子性操作和过期时间设置可以用来实现分布式锁,保证在分布式系统中的资源访问的互斥性。

7 redis持久化方式?rdb与aof?

Redis有两种主要的持久化方式:RDB(Redis Database)和AOF(Append-Only File)。

RDB(Redis Database)持久化方式:RDB持久化是通过将Redis的数据集快照保存到磁盘上的二进制文件中来实现的。它是一个点对点的方式,即在一定的时间间隔内,Redis将内存中的数据以快照的方式写入磁盘。RDB持久化方式适合用于备份和恢复数据,因为它生成的快照文件比较紧凑,占用磁盘空间相对较小。但是,RDB的缺点是如果Redis发生故障,最后一次快照之后的数据会丢失。

AOF(Append-Only File)持久化方式:AOF持久化是通过将Redis的操作命令追加到一个日志文件(AOF文件)中来实现的。当Redis重新启动时,它会重新执行AOF文件中的命令,从而恢复数据。AOF持久化方式适合用于数据的持久化和持久化的安全性,因为它记录了每一条操作命令。但是,由于AOF文件会不断增长,可能会占用较大的磁盘空间。为了解决这个问题,Redis提供了自动压缩和重写AOF文件的功能。

可以根据实际需求选择适合的持久化方式。通常情况下,可以同时使用RDB和AOF持久化方式,以提高数据的安全性和恢复能力。此外,Redis还提供了一种混合持久化方式(AOF + RDB),可以将快照文件和AOF日志文件结合起来使用,以兼顾性能和数据的可靠性。

8 rdb与aof最大的区别?

1、存储方式不同,rdb是以dump.rdb文件存储,而aof是以appendonly.aof形式存储。
2、恢复数据方式不同:rdb恢复数据直接把文件加载到内存,而aof是从新去执行一遍日志记录的指令。
RDB(Redis Database)和AOF(Append-Only File)是Redis中两种不同的持久化方式,它们之间最大的区别在于数据的恢复和安全性方面。

RDB持久化方式:RDB通过在一定时间间隔内将Redis数据集以快照的形式保存到磁盘上的二进制文件中。RDB方式具有以下特点:
数据恢复速度较快:由于RDB是通过直接将数据集写入磁盘,所以在恢复数据时速度较快。
文件大小相对较小:RDB文件是压缩过的二进制文件,相对来说占用较小的磁盘空间。
容易实现定期备份:可以设置定期自动备份数据集。
但是,RDB也有一些缺点:

可能会丢失最后一次快照之后的数据:由于RDB是点对点的方式,Redis发生故障时,最后一次快照之后的数据会丢失。
不适合数据变化频繁的场景:RDB方式适合用于备份和恢复数据,但是对于数据变化频繁的场景,可能会导致较长时间的数据丢失。
AOF持久化方式:AOF通过将Redis的操作命令追加到一个日志文件中来记录数据的变更。AOF方式具有以下特点:
数据更可靠:AOF文件记录了每一条操作命令,因此可以准确地恢复数据。
适用于持久化和恢复数据:AOF方式适合用于数据的持久化和持久化的安全性。
可以通过重写和压缩AOF文件来控制文件大小。
但是,AOF也有一些缺点:

文件大小可能较大:AOF文件是记录了每一条操作命令的文本文件,因此相对来说占用较大的磁盘空间。
数据恢复速度相对较慢:由于需要重新执行AOF文件中的命令,所以在恢复数据时相对较慢。
总结来说,RDB方式适合用于备份和恢复数据,速度快且文件大小较小,但可能会丢失最后一次快照之后的数据;AOF方式适合用于持久化和恢复数据,数据更可靠,但文件大小较大且恢复速度相对较慢。根据具体的应用场景和需求,选择适合的持久化方式。

9 jvm问题?怎么判断一个对象要被回收?

引用计数:每个对象中有一个计数器记录着对象被引用的次数,当为0的时候说明没有被引用,就会被标记回收。
可达性分析:从根节点开始去搜索存在引用的对象,如果在jcRoots中不会被回收,相反会被回收
1、当然判断一个对象是否被回收是引用计数器是否为0,
2、是否被一个可达对象所引用。
3、是否在根集中

在Java中,判断一个对象是否可以被垃圾回收,一般可以依据以下两种方式:

引用计数(Reference Counting):引用计数是一种简单的垃圾回收算法,它通过记录对象的引用数量来判断对象是否可以被回收。每当有一个引用指向对象时,引用计数加1;当引用被解除时,引用计数减1。当引用计数为0时,可以确定对象不再被引用,可以被回收。然而,引用计数算法对于循环引用的情况无法正确处理。

可达性分析(Reachability Analysis):可达性分析是Java虚拟机中常用的垃圾回收算法。它通过判断对象是否与GC Roots(如线程栈中的局部变量、静态变量、JNI引用等)相连来确定对象是否可以被回收。如果对象与GC Roots不可达,则说明该对象无法被应用程序访问到,可以被视为垃圾对象进行回收。

Java虚拟机会自动进行垃圾回收,根据需要来回收不再被引用的对象。一般情况下,开发者无需手动判断对象是否可以被回收,只需遵循Java的垃圾回收机制,合理管理对象的生命周期和引用关系即可。

10 可达性算法中有一个概念叫GC roots知道吗?

jcRoots就是根集,在根集中的对象不会被回收,

11 哪些对象可以被作为垃圾回收的根节点?

虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中的常量引用的对象
方法区中的类静态属性引用的对象
本地方法栈中 JNI(Native 方法)的引用对象
活跃线程(已启动且未停止的 Java 线程)

在Java中,GC Roots(垃圾回收根节点)是一组特殊的对象,它们被认为是活动对象,不会被垃圾回收。GC Roots是Java虚拟机(JVM)用于确定哪些对象是可达的、活动的,并以此为基础进行垃圾回收的起点。

以下是常见的GC Roots的类型:

方法区中的类静态属性引用的对象:被类的静态变量引用的对象,包括被常量池引用的对象。
方法区中的常量引用的对象:被常量引用的对象,如字符串常量、类的静态变量引用的常量对象等。
方法区中的类引用的对象:被加载的类的类对象。
虚拟机栈(线程栈)中的局部变量引用的对象:当前线程执行方法时,局部变量引用的对象。
本地方法栈中JNI引用的对象:本地方法中JNI调用的Java对象。
Java虚拟机内部的全局引用的对象:由JVM内部维护的一些特殊对象引用。
GC Roots是垃圾回收的起点,只有与GC Roots直接或间接关联的对象才会被认为是可达的,不会被垃圾回收。当对象与GC Roots断开关联时,它就成为垃圾对象,可以被垃圾回收器回收。

了解GC Roots的概念有助于理解Java垃圾回收的工作机制和对象的生命周期。在进行内存泄漏分析和优化时,识别和清理无用的GC Roots引用非常重要。

12 一个接口有多个实现的情况,怎么去解决注入对象的准确性?

@Primary注解
@Qualifier注解来解决实现冲突的问题。

13 如果不想用注解去解决还有其他的实现方式吗?

14 在某个service中想要拿到容器对象怎么做?

使用Aware接口下面的实现类
ApplicationContextAware接口拿到容器对象。

15 微服务项目中用到哪些组件?

Eureka作为服务发现,config配置中心,Zuul网管,feign服务之间调用。

16 sping config存在什么问题?

刷新配置需要手动刷新。结合rabbitmq bus
刷新过程,当我们在git上修改了配置,然后通过rabbitmq下发刷新指令,服务收到刷新指令之后去注册中心找到配置中心的地址,在配置中心下载自己的配置。配置中心在git上下载服务配置。

17 用户携带token,在zuul网关需要校验token,怎么自定义逻辑?

实现ZuulFilter类,
1、filterType()方法中选择过滤器类型权限校验使用pre类型的过滤器;
2、在shouldFilter()选择需要权限校验的服务
3、在run()方法中实现过滤逻辑,过滤逻辑就是获取请求上下文对象,拿到用户携带的token做判断。

@Component
public class AccessFilter extends ZuulFilter {
    // 设置过滤器的类型:pre, routing, post, error
    @Override
    public String filterType() {
        // return "pre";
        return FilterConstants.PRE_TYPE;
    }

    // 设置顺序号
    @Override
    public int filterOrder() {
        // 前置过滤器中有5个默认的过滤器,自定义过滤器放到末尾
        // 第5个过滤器中,向上下文对象放入了 serviceId
        // 后面过滤器中才能访问这个数据
        return 6;
    }
    /*
    针对当前请求,是否要执行下面的过滤代码
     */
    @Override
    public boolean shouldFilter() {
        /*
        调用商品,需要判断权限,
        调用用户或订单不检查权限
         */

        // 获得一个请求上下文对象
        RequestContext ctx = RequestContext.getCurrentContext();
        // 从上下文对象获得调用的后台服务的 serviceId
        String serviceId =
            (String) ctx.get(FilterConstants.SERVICE_ID_KEY);// "serviceId"
        // 如果调用的是 item-service,返回true
        return "item-service".equals(serviceId);
    }
    // 过滤代码
    @Override
    public Object run() throws ZuulException {
        // http://localhost:3001/item-service/iuy4tgf3?token=uy4t34t
        // 获得上下文对象
        RequestContext ctx = RequestContext.getCurrentContext();
        // 获得 request 对象
        HttpServletRequest request = ctx.getRequest();
        // 接收 token 参数
        String token = request.getParameter("token");
        // 如果 token 不存在: null, "", "    "
        if (StringUtils.isBlank(token)) {
            // 阻止继续调用
            ctx.setSendZuulResponse(false);
            // 直接返回响应
            // JsonResult - {code:400, msg:未登录, data:null}
            String json = JsonResult
                                .build()
                                .code(400)
                                .msg("Not Login! 未登录!")
                                .toString();
            ctx.addZuulResponseHeader(
                    "Content-Type", "application/json;charset=UTF-8");
            ctx.setResponseBody(json);
        }

        return null; //zuul当前版本这个返回值不起任何作用
    }
}

18 zuul相对于gateway的缺点有哪些?

zuul的局限性,底层使用了servlet,存在阻塞IO的问题,gateway使用了netye。

19 rabbitmq怎么保证消息可靠性?

1、生产者丢失消息:从生产者弄丢数据这个角度来看,RabbitMQ提供transaction和confirm模式来确保生产者不丢消息;
2、消息队列丢数据:消息持久化。
处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。
这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。
这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。
那么如何持久化呢?
这里顺便说一下吧,其实也很容易,就下面两步
将queue的持久化标识durable设置为true,则代表是一个持久的队列
发送消息的时候将deliveryMode=2
这样设置以后,即使rabbitMQ挂了,重启后也能恢复数据
3、消费者处理消息成功后,手动回复确认消息。

20 rabbitmq怎么实现手动确认?

手动确认就是消息消费完之后给队列一个回执,消息队列收到回执后确认消息被消费,才会删除消息。否则回进行消息回滚把消息分配给其他消费者
//定义一个新的队列,名为 task_queue
//第二个参数是持久化参数 durable
ch.queueDeclare(“task_queue”, true, false, false, null);

//第三个参数设置消息持久化
ch.basicPublish(“”, “task_queue”,
MessageProperties.PERSISTENT_TEXT_PLAIN,
msg.getBytes());

2021-11-29

1 分布式事务有做过吗?

2 网管用的什么?

3 你在项目中怎么保证线程安全?

在项目中保证线程安全可以使用以下几种方式:

同步锁(Synchronized):使用Synchronized关键字来保护共享资源的访问,通过加锁和释放锁的机制来确保同一时间只有一个线程可以访问共享资源,从而避免竞态条件和数据不一致的问题。

互斥锁(Lock):使用Lock接口及其实现类(如ReentrantLock)来实现显式的锁定和解锁操作。通过在代码块中使用lock()方法获取锁,然后在finally块中使用unlock()方法释放锁,来确保线程安全的访问共享资源。

原子操作(Atomic):使用原子操作类(如AtomicInteger、AtomicLong等)来进行原子性的读取和更新操作,避免了使用锁的开销,并且保证了操作的原子性。

并发集合(Concurrent Collection):使用线程安全的集合类(如ConcurrentHashMap、CopyOnWriteArrayList等)来替代非线程安全的集合类,在并发情况下提供安全的访问和修改操作。

线程安全的设计模式:使用线程安全的设计模式(如单例模式中的双重检查锁定、读写锁模式等)来保证在多线程环境下的线程安全性。

使用线程池(ThreadPool):使用线程池来管理和调度线程的执行,避免了手动创建线程的开销,并提供了线程安全的执行环境。

需要根据具体的场景和需求选择合适的方式来保证线程安全。同时,对于多线程环境下的共享资源,还需要进行合适的同步和协作机制的设计,以确保线程之间的正确交互和数据一致性。

4 购物车怎么设计订单过期时间?

在购物车中设计订单过期时间,可以考虑以下几种方式:

基于时间戳:为每个订单设置一个过期时间戳,表示订单的有效期限。当订单创建时,记录当前的时间戳加上一段固定的时间(例如30分钟)作为订单的过期时间戳。在后续处理订单的过程中,可以通过比较当前时间戳和订单的过期时间戳来判断订单是否过期。

定时任务检查:使用定时任务来检查订单的过期情况。定时任务可以定期扫描购物车中的订单,检查订单的创建时间和当前时间的差值是否超过一定的时间阈值(例如30分钟),如果超过了,则将订单标记为过期订单,并进行相应的处理。

绑定用户会话:将订单与用户会话绑定,并在用户会话过期后自动清除相关订单。当用户添加商品到购物车时,可以将商品和订单信息存储在用户的会话中,并设置会话的过期时间。当用户会话过期后,相关订单信息也会自动清除,从而实现订单的过期处理。

需要根据具体的业务需求和系统架构选择合适的方式来设计订单过期时间。无论选择哪种方式,都需要确保在订单过期后及时进行相应的处理,例如从购物车中移除过期订单、释放相关资源等,以确保系统的正常运行和用户体验。

5 RabbitMq数据流转过程?

6 sheep与waite有什么区别?

最大的不同是在等待时 wait 会释放锁,而 sleep 一直持有锁。Wait 通常被用于线
程间交互,sleep 通常被用于暂停执行。
yield()方法仅释放 CPU 执行权,锁仍然占用,线程会被放入就绪队列,会在短时
间内再次执行。

wait()方法会释放 CPU 执行权 和 占有的锁。
sleep(long)方法仅释放 CPU 使用权,锁仍然占用;线程被放入超时等待队列,与
yield 相比,它会使线程较长时间得不到运行。
yield()方法仅释放 CPU 执行权,锁仍然占用,线程会被放入就绪队列,会在短时
间内再次执行。
wait 和 notify 必须配套使用,即必须使用同一把锁调用;
wait 和 notify 必须放在一个同步块中调用 wait 和 notify 的对象必须是他们所处
同步块的锁对象。

7 怎么唤醒阻塞线程?

notify() 方法不能唤醒某个具体的线程,所以只有一个线程在等待的时候它才有
用武之地。
而 notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。

8 创建线程的方式?

1、继承Thread类重写run方法
2、实现Runable中的run方法
3、实现Callable中的call方法

9 乐观锁的实现

10 分布式锁怎么解决锁过期?

11 redis怎么删除过期数据?

定时删除,定期删除,惰性删除默认,用到的时候才去检查key是否过期

12 线程池

线程池的构造方法
int corePoolSize,核心线程数
int maximumPoolSize,最大线程数
long keepAliveTime,超过最大线程数空闲时间
TimeUnit unit,时间单位
BlockingQueue workQueue 等待队列,双向FIFO
ThreadFactory threadFactory 创建线程的工厂
RejectedExecutionHandler handler拒绝策略
创建线程的工厂,以及拒绝策略可以自己指定,所以构造方法有四种。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

13 线程池执行流程

在这里插入图片描述

14 四种任务拒绝策略

AbortPolicy:抛异常不执行新任务
DisCardPolicy:不抛任务不执行新任务
DisCardOldSetPolicy:移除队列最早的任务,加入新线程
CallerRunsPolicy:重新添加任务,调用execute

13 spring事务

REQUIRED
Spring默认的传播机制,如果外层有事务,则当前事务加入到外层事务,如果外层没有事务,新建一个事务执行
REQUES_NEW
如果外层有事务则挂起。如果外层没有事务,执行当前新开启的事务即可
SUPPORT
如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。
NOT_SUPPORT
以非事务运行,如果存在事务则挂起事务
NEVER
该传播机制不支持外层事务,即如果外层有事务就抛出异常
MANDATORY
与NEVER相反,如果外层没有事务,则抛出异常,必须以事务运行
NESTED
该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。

spring

14 数据库如何实现乐观锁与悲观锁,项目中怎么用的

乐观锁:加版本号
悲观锁:for update
如果for update 使用了索引就锁行,没有使用索引就锁表

15 数据库存储引擎的区别?

16 JMM线程模型

17 对JVM调优了解吗?

18 JVM区域划分?

19 方法区1.7与1.8实现的区别?

20 你对哪种设计模式用的最多?说一下怎么实现的

21 双亲委派机制知道吗?

22 springBoot使用多线程怎么使用?

23 你使用多线程会直接new Thread吗

24 线程池怎么使用的?

25 能指定的队列之间有什么区别吗?都在那种场景下使用

26 synchronized加锁的原理?

27 synchronized两个字节码指令怎么作用的?

28 说一下缓存的一致性?

29 volatile 怎么保证可见性?volatile为什么能保证可见性?

30FutureTask 有用过吗?

31 synchronized 与 ReentrantLock

synchronized 是java关键字,ReentrantLock是一个类
synchronized 无法判断锁的状态,ReentrantLock可以判断是否获取到锁
synchronized 会自动释放锁,ReentrantLock必须要手动释放锁!如果不释放锁,会造成死锁
synchronized 线程1(获得锁,阻塞),线程2会等待,ReentrantLock中线程不会一直等待,会尝试获取锁TrayLok()
synchronized 可重入锁,不可中断,非公平;ReentrantLock 可重入,可以判断锁,非公平(可以自己设置)
synchronized 适合锁少量的代码同步问题,ReentrantLock适合锁大量的同步代码

32 mysql调优

33 说一下分库分表?

34 mysql 的本地事务和分布式事务都有过吗?

35 本地事务使用的场景?

36 springboot使用本地事务?@Transcation

37 @Transcation中的参数有用过吗?

38 redis用的什么版本的?怎么选择的?

39 redis为什么使用单线程来处理业务?

面试官说还是为了最求极致的效率,多线程有额外的消耗,在单线程下保持高效率的话类似于时间片的分能。在很短的时间完成这个任务。

40 能说一下哨兵和集群的区别吗?

41 rabbitMq怎么保证消息的可靠性?

1、保证消息不丢失(三步)
1.1、开启事务(不推荐)
1.2、开启confirm(推荐)
1.3、开启RabbitMQ持久化(交换机、队列、消息)
1.4、关闭RabbitMQ自动ack(改成手动)
2、保证消息不重复消费
2.1、幂等性(每个消息用一个唯一标识来区分,消费前先判断标识有没有被消费过,若已消费过,则直接ACK)
3、RabbitMQ如何保证消息的顺序性
将消息放入同一个交换机,交给同一个队列,这个队列只有一个消费者,消费者只允许同时开启一个线程
4、RabbitMQ消息重试机制
消费者在消费消息的时候,如果消费者业务逻辑出现程序异常,这时候应该如何处理?
答案:使用消息重试机制(SpringBoot默认3次消息重试机制)

41 rabbitmq与roboimq,为什么会选择rabbitmq?

42 你们会把什么数据发送到kafka中?

43 你们的kafka有掉过消息吗?

44 常见的错误类型

ConcurrentModificationException:并发修改异常 在并发下集合类线程不安全
StackOverflowException 栈溢出异常

45 集合线程不安全怎么解决?

方案1:使用线程安全的集合Vector,但是这个集合内部直接使用synchronized保证线程安全,导致效率低。
方案2:使用集合类的顶层接口Collections的静态内部类
方案3:CopyOnWrite 写入时复制,多个线程调用list的时候,读取是固定的,写入的时候存在覆盖的情况
在写入的避免覆盖,造成数据错误问题。
它完成了读写分离,提高效率。
CopyOnWriteArrayList 比 Vector 效率高的地方,Vector所有的方法加了synchronized效率低,CopyOnWriteArrayList 底层加的Lock锁的方式保证线程安全。

public class LlistTest {
    public static void main(String[] args) {
        ArrayList<String> list1 = new ArrayList<>();
        Vector<String> list2 = new Vector<>();//解决方案一
        List<String> list = Collections.synchronizedList(new ArrayList<>());//解决方案二
        CopyOnWriteArrayList<String> list3 = new CopyOnWriteArrayList<>();//解决方案三

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

46 redis怎么排序

48 JVM内存模型

49 rabitMQ怎么保证消息可靠性

50 sql慢查询

51 线程池阻塞队列有哪几种?

52 hashmap红黑树有什么弊端?

53 如果不想触发频繁fulljc怎么办?

可以通过调整MaxTenuringThreshold=15来限制对象添加老年代。

54 LinkedBlockingQueue 与ArrayBlockingQueue的区别?

1.队列大小有所不同,ArrayBlockingQueue是有界的初始化必须指定大小,而LinkedBlockingQueue可以是有界的也可以是无界的(Integer.MAX_VALUE),(而且不会初始化就占用一大片内存)对于后者而言,当添加速度大于移除速度时,在无界的情况下,可能会造成内存溢出等问题。
2.数据存储容器不同,ArrayBlockingQueue采用的是数组作为数据存储容器,而LinkedBlockingQueue采用的则是以Node节点作为连接对象的链表。
3.两者的实现队列添加或移除的锁不一样,ArrayBlockingQueue实现的队列中的锁是没有分离的,即添加操作和移除操作采用的同一个ReenterLock锁,而LinkedBlockingQueue实现的队列中的锁是分离的,其添加采用的是putLock,移除采用的则是takeLock,这样能大大提高队列的吞吐量,也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。

55 Redis是单线程的为什么还那么快?

1.redis是基于内存的,内存的读写速度非常快;
2.redis是单线程的,省去了很多上下文切换线程的时间;
3.redis使用多路复用技术,可以处理并发的连接。非阻塞IO 内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间。

56 如果不希望频繁触发FullGC?

可以通过调整to区到from区复制次数,来控制进入老年代的对象
可以通过调整MaxTenuringThreshold=15默认是15,可以调大

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值