技能介绍

Java 和多线程开发:

四种方式实现多线程

  1. 继承 Thread 类创建线程

       public class MyThread extends Thread {  
    public void run() {
    System.out.println("MyThread.run()");
    }
    }

    MyThread myThread1 = new MyThread();
    MyThread myThread2 = new MyThread();
    myThread1.start();
    myThread2.start();

    1. **实现Runnable接口创建线程**

    ```java
    public class MyThread extends OtherClass implements Runnable {
    public void run() {
    System.out.println("MyThread.run()");
    }
    }

    MyThread myThread = new MyThread();
    Thread thread = new Thread(myThread);
    thread.start();
  2. 实现Callable接口通过FutureTask包装器来创建Thread线程

    public class SomeCallable<V> extends OtherClass implements Callable<V> {
    @Override
    public V call() throws Exception {
    // TODO Auto-generated method stub
    return null;
    }
    }

    Callable<V> oneCallable = new SomeCallable<V>();
    FutureTask<V> oneTask = new FutureTask<V>(oneCallable);
    Thread oneThread = new Thread(oneTask);
    oneThread.start();
  3. 使用ExecutorService、Callable、Future实现有返回结果的线程

       import java.util.concurrent.*;  
    import java.util.Date;
    import java.util.List;
    import java.util.ArrayList;

    public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    System.out.println("----程序开始运行----");
    Date date1 = new Date();

    int taskSize = 5;
    ExecutorService pool = Executors.newFixedThreadPool(taskSize);
    List <Future> list = new ArrayList <Future>();

    for (int i = 0; i < taskSize; i++) {
    Callable c = new MyCallable(i + " ");
    Future f = pool.submit(c);
    list.add(f);
    }

    pool.shutdown();

    for (Future f : list) {
    System.out.println(">>>" + f.get().toString());
    }

    Date date2 = new Date();
    System.out.println("----程序结束运行----,程序运行时间【" + (date2.getTime() - date1.getTime()) + "毫秒】");
    }
    }

    class MyCallable implements Callable <Object> {
    private String taskNum;

    MyCallable(String taskNum) {
    this.taskNum = taskNum;
    }

    public Object call() throws Exception {
    System.out.println(">>>" + taskNum + "任务启动");
    Date dateTmp1 = new Date();
    Thread.sleep(1000);
    Date dateTmp2 = new Date();
    long time = dateTmp2.getTime() - dateTmp1.getTime();
    System.out.println(">>>" + taskNum + "任务终止");
    return taskNum + "任务返回运行结果, 当前任务时间【" + time + "毫秒】";
    }
    }





    # 多线程相关知识

    1. **Runnable 和 Callable 的区别?**

    - 主要区别:
    - Runnable 接口 run 方法无返回值;
    - Callable 接口 call 方法有返回值,支持泛型。

    - 异常处理:
    - Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;

    - Callable 接口 call 方法允许抛出异常,可以获取异常信息。


    运行时异常通常是由程序逻辑错误引起的,例如除零操作、数组越界、空指针引用等。

    换言之,Runable 比较保守,啥也没有。

    1. **如何启动一个新线程、调用 start 和 run 方法的区别?**
    - 调用 run 方法不开启线程,仅是对象调用方法。
    - 调用 start 方法开启线程,并让 JVM 调用 run 方法在开启的线程中执行。调用 start 方法可以启动线程,并使得线程进入就绪状态,而 run 方法只是 thread 的一 个普通方法,还是在主线程中执行。
    2. **线程相关的基本方法?**
    - wait:线程进入 waiting 状态,释放对象锁。
    - sleep:线程进入 TIMED-WATING 状态,不释放对象锁。
    - yield:线程让出 CPU 执行时间片。
    - join:等待其他线程终止。
    - interrupt:中断一个线程。
    - 不会直接停止线程的执行,而是设置了中断标志位。
    - notify:唤醒在此对象监视器上等待的单个线程。
    - notifyAll:唤醒在此对象监视器上等待的所有线程。
    3. **wait() 和 sleep() 的区别?**
    - 来自不同的类:wait() 来自 Object 类;sleep() 来自 Thread 类。
    - 关于锁的释放:wait() 在等待的过程中会释放锁;sleep() 在等待的过程中不会释放锁。
    - 使用的范围:wait() 必须在同步代码块中使用;sleep() 可以在任何地方使用。
    - 同步代码块:同步代码块通过关键字 `synchronized` 来定义,将一段代码包装在同步块中,以确保在同一时刻只有一个线程可以执行该代码块。
    - 是否需要捕获异常:wait() 不需要捕获异常;sleep() 需要捕获异常。
    4. **多线程原理**
    - 多线程是通过并发的方式进行。在某个时间点上,CPU 只能执行一个程序,即同一时间只能运行一个进程。CPU 在多个进程之间切换执行,每个线程执行一段时间。
    - 多线程技术主要解决处理器单元内多个线程执行的问题,提高处理器单元的吞吐能力。





    # ReentrantLock 底层实现:

    `ReentrantLock` 是 Java 中提供的一种高级的线程同步机制,其底层实现涉及到多个概念和数据结构。

    ## 1. 可中断性:

    `ReentrantLock` 实现了可中断性,这意味着线程在等待锁的过程中,可以被其他线程中断而提前结束等待。在底层,`ReentrantLock` 使用了与 `LockSupport.park()` 和 `LockSupport.unpark()` 相关的机制来实现可中断性。

    ## 2. 设置超时时间:

    `ReentrantLock` 支持在尝试获取锁时设置超时时间,即等待一定时间后如果还未获得锁,则放弃锁的获取。这是通过内部的 `tryAcquireNanos` 方法来实现的。

    ## 3. 公平锁和非公平锁:

    在直接创建 `ReentrantLock` 对象时,默认情况下是非公平锁。公平锁是按照线程等待的顺序来获取锁,而非公平锁则允许多个线程在同一时刻竞争锁,不考虑它们申请锁的顺序。公平锁可以通过在创建 `ReentrantLock` 时传入 `true` 来设置,例如:

    ```java
    ReentrantLock fairLock = new ReentrantLock(true);

4. 多个条件变量:

ReentrantLock 支持多个条件变量,每个条件变量可以与一个 ReentrantLock 关联。这使得线程可以更灵活地进行等待和唤醒操作,而不仅仅是基于对象监视器的 wait()notify()。多个条件变量的实现依赖于 Condition 接口,例如:

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// 使用下面方法进行等待和唤醒
condition.await();
condition.signal();

5. 可重入性:

ReentrantLock 支持可重入性,即同一个线程可以多次获得同一把锁,而不会造成死锁。这是通过内部的 holdCount 计数来实现的。当一个线程多次获取锁时,holdCount 递增,释放锁时递减,只有当 holdCount 为零时,其他线程才有机会获取锁。

底层机制:

ReentrantLock 的底层实现主要依赖于 AbstractQueuedSynchronizer(AQS)这个抽象类。AQS 是一个提供了基本同步机制的框架,其中包括了队列、状态值等。ReentrantLock 在 AQS 的基础上通过内部类 Sync 来实现具体的锁操作。不同的 Sync 子类实现了公平锁和非公平锁的不同逻辑。

总体而言,ReentrantLock 是一个功能强大且灵活的锁,其底层实现借助了 Java 并发包提供的底层机制,如 AQS、LockSupport 等。这些机制使得 ReentrantLock 能够提供多种高级的同步功能。

应用场景:

可重入锁的主要应用场景是在需要同步控制多个层次的嵌套代码执行时,以及递归调用中需要多次获得同一把锁的情况。这种锁的特性使得同一个线程在获取锁的时候不会被阻塞,而是能够继续获取相同的锁,从而避免了死锁的发生。

  1. 递归调用: 当一个方法递归地调用自身,而这个方法在执行过程中需要获取某个锁时,可重入锁就能够确保同一个线程在递归调用中能够多次获得同一把锁,而不会被阻塞。

  2. 嵌套的同步代码块: 当一个方法中包含多个同步代码块,并且这些同步代码块之间存在嵌套关系时,可重入锁能够确保同一个线程在嵌套执行过程中能够多次获得同一把锁。

  3. 锁的可重入性: 在一个类的多个方法中使用同一把锁,而这些方法之间存在调用关系时,可重入锁能够确保同一个线程在不同方法中能够多次获得同一把锁。

JVM底层原理:

看这个

Redis:

  • 你熟悉Redis,了解其缓存使用场景,包括缓存持久化和淘汰策略。
  • 你了解如何使用Redis实现分布式锁,这是处理分布式系统中并发访问的重要技术。

Redis 作为一款内存数据库,支持多种缓存持久化和淘汰策略,以确保数据的持久性和有效管理内存。以下是关于 Redis 的缓存持久化和淘汰策略的详细介绍:

缓存持久化:

1. RDB 持久化(Snapshotting):

  • 原理: 将 Redis 在某个时间点的数据集快照写入磁盘。这样可以定期保存整个数据集,适合用于备份和灾难恢复。
  • 配置:redis.conf 文件中配置 save 参数来设置触发快照的条件,以及指定快照文件的保存路径。

2. AOF 持久化(Append-Only File):

  • 原理: 将 Redis 执行的所有写操作追加到一个文件中,以恢复数据集的方式。AOF 文件中包含了可以重放数据集的所有写命令,因此更安全。
  • 配置:redis.conf 文件中开启 AOF 持久化,配置 AOF 文件名、刷写频率等参数。

3. 混合持久化:

  • 可以同时开启 RDB 和 AOF 持久化,以兼顾 RDB 的快照备份和 AOF 的操作追加的优势。

缓存淘汰策略:

1. LRU(Least Recently Used)

  • 原理: 根据数据的最近使用时间来淘汰最少使用的数据。Redis 使用 volatile-lruallkeys-lru 两种 LRU 淘汰策略。
  • 配置:redis.conf 文件中配置 maxmemory-policy 参数为 volatile-lruallkeys-lru

2. LFU(Least Frequently Used)

  • 原理: 根据数据的使用频率来淘汰最不频繁使用的数据。Redis 使用 volatile-lfuallkeys-lfu 两种 LFU 淘汰策略。
  • 配置:redis.conf 文件中配置 maxmemory-policy 参数为 volatile-lfuallkeys-lfu

3. 随机淘汰:

  • 原理: 随机选择一个 key 进行淘汰。
  • 配置:redis.conf 文件中配置 maxmemory-policy 参数为 volatile-randomallkeys-random

4. 定期淘汰:

  • 原理: 定期检查 key 的过期时间,将过期的 key 进行淘汰。
  • 配置:redis.conf 文件中配置 maxmemory-policy 参数为 volatile-ttlallkeys-ttl

5. 不淘汰策略:

  • 原理: 当达到内存上限时,新写入的数据将被拒绝。适用于要求数据完整性的场景。

以上这些持久化和淘汰策略可以根据具体需求进行灵活配置。在实际应用中,通常会结合业务需求和硬件资源情况来选择适当的配置。

缓存使用场景

缓存

缓存如今几乎成为所有中大型网站必备的技术武器。合理利用缓存不仅能够提升网站访问速度,还能显著降低数据库的负载。Redis不仅提供键过期功能,还支持灵活的键淘汰策略,因此在缓存场景下,Redis的应用非常广泛。

排行榜

许多网站都涉及排行榜应用,比如京东的月度销量榜单、商品按时间的上新排行榜等。Redis提供的有序集合数据结构能够实现各种复杂的排行榜应用,使其变得轻松而高效。

计数器

计数器是指在电商网站商品的浏览量、视频网站视频的播放数等应用场景。为了保证数据的实时性,每次浏览都需要进行+1操作。在高并发场景下,频繁请求数据库操作是一项巨大的挑战。Redis的incr命令提供了一种内存操作方式,性能出色,非常适用于计数场景。

分布式会话

在集群模式下,当应用规模较大时,一般会使用以Redis等内存数据库为中心的session服务,而不再依赖容器自带的session复制功能。这种方式更适合于复杂的系统,其中session的管理由专门的服务及内存数据库来处理。

分布式锁

在许多互联网公司中,分布式技术带来了对同一资源的并发访问的挑战。针对高并发场景,使用数据库锁会对性能造成较大影响。Redis的setnx功能可用于编写分布式锁,通过判断返回值是否为1来判断是否成功获取锁。在实际应用中需要更多细节的考虑。

社交网络

社交网络的基本功能包括点赞、踩、关注/被关注、共同好友等。由于传统的关系数据库不太适合存储这种类型的数据,Redis提供的哈希、集合等数据结构非常方便实现这些功能,尤其适用于高访问量的社交网站。

最新列表

通过Redis的列表结构,可以使用LPUSH在列表头部插入一个内容ID作为关键字,再利用LTRIM限制列表的数量,使列表永远保持N个ID,无需查询最新的列表,直接根据ID访问对应的内容页即可。

消息系统

消息队列是大型网站中不可或缺的中间件,如ActiveMQ、RabbitMQ、Kafka等。这些消息队列用于实现业务解耦、流量削峰以及异步处理实时性较低的业务。虽然Redis提供了发布/订阅及阻塞队列功能,可以实现一个简单的消息队列系统,但与专业的消息中间件相比,仍有一定差距。

Spring和Spring Boot:

SpringSpring BootSpring MVC是Spring框架的不同模块,各自有不同的目标和关注点。

  1. Spring:

    • 目标: Spring是一个综合性的企业级Java框架,旨在简化Java应用程序的开发,提供了广泛的功能,包括IoC(控制反转)、AOP(面向切面编程)、事务管理、数据访问等。
    • 特点: Spring框架提供了一个容器,用于管理Java对象的生命周期和配置,同时提供了各种模块用于不同方面的开发,如Spring Core、Spring JDBC、Spring ORM等。
  2. Spring Boot:

    • 目标: Spring Boot是在Spring基础之上构建的,旨在简化和加速Spring应用程序的开发和部署。它采用了“约定大于配置”的理念,提供了自动化配置和快速开发的特性。
    • 特点: Spring Boot通过默认的配置和自动化的方式,减少了繁琐的配置工作,提供了内嵌的Web服务器(如Tomcat、Jetty)、开箱即用的各种依赖库、自动化构建工具等,使得开发者可以更专注于业务逻辑的编写,而不必过多关注底层的配置。
  3. Spring MVC:

    • 目标: Spring MVC是Spring框架的Web模块,专注于构建Web应用程序,提供了MVC(Model-View-Controller)架构。
    • 特点: Spring MVC通过注解或XML配置的方式,定义了请求映射、数据绑定、视图解析等,适用于构建传统的Web应用程序。它可以与其他Spring模块(如Spring Boot)结合使用,用于处理Web层的逻辑。

总结区别:

  • Spring: 是一个综合性的企业级Java框架,提供了广泛的功能,需要手动配置。
  • Spring Boot: 是在Spring基础上构建的,旨在简化和加速Spring应用程序的开发,提供了自动化配置和快速开发的特性。
  • Spring MVC: 是Spring框架的Web模块,专注于构建Web应用程序,提供了MVC架构,适用于传统的Web应用程序开发。可以与其他Spring模块结合使用。

MySQL:

InnoDB存储引擎:

  • 特点: InnoDB是MySQL数据库的一种存储引擎,具有事务支持、行级锁定和外键等特性。它被设计为具有高并发性能和数据完整性的存储引擎。
  • 事务支持: InnoDB存储引擎支持ACID属性(原子性、一致性、隔离性、持久性),允许用户执行复杂的事务操作。
  • 行级锁定: InnoDB使用行级锁定,而不是表级锁定,提高了并发性能,允许多个事务同时访问同一表的不同行。

事务隔离级别:
MySQL支持四种事务隔离级别,分别是Read Uncommitted(读取未提交数据)、Read Committed(读取已提交数据)、Repeatable Read(可重复读)、Serializable(串行化)。不同的隔离级别提供不同程度的数据一致性和并发性。

锁机制:

  • 行级锁定: InnoDB存储引擎使用行级锁定,使得多个事务可以在同一表中同时进行读写操作而不互相阻塞。
  • 共享锁和排他锁: 事务可以对数据行设置共享锁(允许其他事务读取但不允许写入)或排他锁(禁止其他事务读取或写入)。
  • 锁的粒度: InnoDB锁的粒度可以是行级锁、页级锁或表级锁,但一般而言,它使用更细粒度的行级锁。

MVCC(多版本并发控制):

  • 原理: MVCC是InnoDB用于实现事务隔离级别的机制。它通过在每一行记录上保存多个版本,使得每个事务在读取数据时都能看到一致性的数据快照,从而提高并发性。
  • 版本号: 每个事务写入一行数据时,InnoDB为该行记录赋予一个唯一的版本号。事务开始时,数据库系统记录事务的开始时间戳,每个事务读取数据时,都会根据自己的时间戳来选择对应版本的数据。
  • 读操作: 读操作不会对其他事务造成影响,因为它读取的是数据快照,不会阻塞其他事务的写操作。
  • 写操作: 写操作会生成新的数据版本,并在数据行上加锁,以确保其他事务在写操作进行时不会读取到正在被修改的数据。

总结:
InnoDB存储引擎通过使用行级锁和MVCC机制,实现了高并发性能和事务隔离级别。锁机制保证了数据的一致性,MVCC机制则提高了并发性。事务隔离级别和锁机制可以根据具体的应用场景进行调整,以平衡性能和数据一致性的需求。

设计模式:

吃得饱外卖

这是一份涵盖了多个方面的系统设计和实现描述。以下是对每个点的简要总结:

订单、用户、菜品管理模块设计与实现:

  • 通过使用 MyBatis-Plus 进行数据库操作,说明了对订单、用户、菜品等业务模块的设计和实现。
  • 可能包括数据库表的设计、ORM(对象关系映射)的使用,以及使用 MyBatis-Plus 简化 CRUD 操作。

Redis热点数据缓存:

  • 利用 Redis 实现热点数据缓存,提高系统性能和用户体验。
  • 可能包括识别热点数据、缓存策略的选择、缓存更新策略等。

前后端联调与性能优化:

  • 通过前后端联调,调整和优化 Vue 中发送的 Ajax 请求路径和方法,以提高系统性能。
  • 可能包括减少请求次数、减小数据传输量、采用异步加载等方式进行性能优化。

处理并发请求的线程安全设计:

  • 利用 ThreadLocal 在服务端新建线程时存取用户 ID,确保在并发环境中的线程安全。
  • 这是为了处理并发请求时,每个线程都能独立地访问和修改自己的用户信息,避免数据混乱。

阿里云服务器部署与代码版本管理:

  • 独立完成项目在阿里云 Linux 服务器上的部署,可能包括服务器环境配置、依赖安装等。
  • 使用 Git 进行代码版本管理,有可能包括分支管理、版本发布等。
  • 使用 Nginx 实现反向代理,可能包括负载均衡、安全性配置等。

这份描述展示了你在全栈开发中的综合能力,从数据库设计到前后端性能优化,再到部署和服务器管理,都涉及到了多个关键方面。

RPC

这是一个基于 Spring Boot、Netty 和 Zookeeper 的项目,具有多项亮点和特性:

多种网络通信方式:

  • 使用 Netty、Socket 和 Http 三种方式进行网络通信,展示了多样化的通信手段。

自定义消息协议和编解码器:

  • 实现了自定义消息协议,以及相应的编解码器,使通信更高效和可控。

多种序列化算法支持:

  • 支持五种序列化算法,包括 JDK、JSON、HESSIAN、KRYO 和 PROTOSTUFF。

多种负载均衡算法:

  • 实现了三种负载均衡算法,包括 RoundRobin、Random 和 ConsistentHash。

多种动态代理方式:

  • 实现了两种动态代理,包括 JDK 和 CGLIB,以提供更灵活的代理选择。

基于 Zookeeper 的服务注册与发现:

  • 使用 Zookeeper 实现了服务注册与发现功能,并增加了服务本地缓存与监听,提高了系统的可用性和稳定性。

集成 Spring 框架:

  • 通过自定义注解提供 RPC 组件扫描、服务注册和服务消费功能,使整合更加方便。

集成 Spring Boot 并完成自动配置:

  • 实现了与 Spring Boot 的集成,完成了自动配置,简化了项目的搭建和使用。

Netty 心跳机制:

  • 引入了 Netty 心跳机制,以复用 Channel 连接,提高了网络连接的效率和稳定性。

自定义 SPI 机制:

  • 实现了自定义 SPI(Service Provider Interface)机制,扩展了项目的可插拔性。

高并发支持:

  • 在项目中实现了支持 10000 个线程同时发起 RPC 调用,吞吐量在 29300 上下,显示了系统在高并发环境下的性能。