面试题:用过ThreadLocal吗?ThreadLocal是在哪个包下的?看过ThreadLocal源码吗?讲一下ThreadLocal的get和put是怎么实现的?

简介: 字节面试题:用过ThreadLocal吗?ThreadLocal是在哪个包下的?看过ThreadLocal源码吗?讲一下ThreadLocal的get和put是怎么实现的?

面试题:用过ThreadLocal吗?ThreadLocal是在哪个包下的?看过ThreadLocal源码吗?讲一下ThreadLocal的get和put是怎么实现的?


介绍ThreadLocal


ThreadLocal是Java中的一个线程封闭工具,允许线程在其范围内创建一个本地变量。每个线程都有自己的变量副本,这使得线程可以独立地访问自己的变量副本,而不会与其他线程的变量发生冲突。ThreadLocal通常用于保存线程私有的上下文信息,如数据库连接、会话信息等。


ThreadLocal所在的包


ThreadLocal类位于Java的java.lang包下。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 使用ThreadLocal模拟数据库连接池的测试方法
 */
public class ThreadLocalTest {
    // 模拟数据库连接对象
    static class Connection {
        // 数据库连接ID
        private final String connectionId;

        public Connection(String connectionId) {
            this.connectionId = connectionId;
        }

        public String getConnectionId() {
            return connectionId;
        }
    }

    // 模拟数据库连接池
    static class ConnectionPool {
        // 使用ThreadLocal存储数据库连接
        private static final ThreadLocal<Connection> threadLocalConnection = ThreadLocal.withInitial(() -> new Connection("Connection-1"));

        // 获取数据库连接
        public static Connection getConnection() {
            return threadLocalConnection.get();
        }

        // 归还数据库连接(这里简化为清除ThreadLocal中的连接)
        public static void releaseConnection() {
            threadLocalConnection.remove();
        }
    }

    // 模拟公共数据集
    static class SharedDataSet {
        // 使用ThreadLocal存储数据集
        private static final ThreadLocal<String> threadLocalDataSet = new ThreadLocal<>();

        // 设置数据集
        public static void setDataSet(String dataSet) {
            threadLocalDataSet.set(dataSet);
        }

        // 获取数据集
        public static String getDataSet() {
            return threadLocalDataSet.get();
        }

        // 清除数据集
        public static void clearDataSet() {
            threadLocalDataSet.remove();
        }
    }

    // 模拟数据库查询任务
    static class DatabaseQueryTask implements Runnable {
        @Override
        public void run() {
            // 从连接池获取数据库连接
            Connection connection = ConnectionPool.getConnection();
            System.out.println("线程 " + Thread.currentThread().getName() + " 使用连接进行查询: " + connection.getConnectionId());

            // 模拟数据库查询
            try {
                System.out.println("线程 " + Thread.currentThread().getName() + " 开始模拟数据库查询...");
                Thread.sleep(1000);
                System.out.println("线程 " + Thread.currentThread().getName() + " 完成模拟数据库查询!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 获取数据集并输出
            String dataSet = SharedDataSet.getDataSet();
            System.out.println("线程 " + Thread.currentThread().getName() + " 获取数据集: " + dataSet);

            // 设置数据集并输出
            SharedDataSet.setDataSet("Data from " + Thread.currentThread().getName());
            System.out.println("线程 " + Thread.currentThread().getName() + " 设置数据集: Data from " + Thread.currentThread().getName());

            // 归还数据库连接
            ConnectionPool.releaseConnection();
            System.out.println("线程 " + Thread.currentThread().getName() + " 归还连接: " + connection.getConnectionId());
        }
    }

    public static void main(String[] args) {
        // 创建一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        // 提交多个数据库查询任务
        for (int i = 0; i < 10; i++) {
            executorService.submit(new DatabaseQueryTask());
        }

        // 关闭线程池
        executorService.shutdown();
    }
}

  • 运行结果
线程 pool-1-thread-2 使用连接进行查询: Connection-1
线程 pool-1-thread-2 开始模拟数据库查询...
线程 pool-1-thread-4 使用连接进行查询: Connection-1
线程 pool-1-thread-4 开始模拟数据库查询...
线程 pool-1-thread-1 使用连接进行查询: Connection-1
线程 pool-1-thread-1 开始模拟数据库查询...
线程 pool-1-thread-5 使用连接进行查询: Connection-1
线程 pool-1-thread-5 开始模拟数据库查询...
线程 pool-1-thread-3 使用连接进行查询: Connection-1
线程 pool-1-thread-3 开始模拟数据库查询...
线程 pool-1-thread-2 完成模拟数据库查询!
线程 pool-1-thread-2 获取数据集: null
线程 pool-1-thread-2 设置数据集: Data from pool-1-thread-2
线程 pool-1-thread-4 完成模拟数据库查询!
线程 pool-1-thread-4 获取数据集: null
线程 pool-1-thread-4 设置数据集: Data from pool-1-thread-4
线程 pool-1-thread-1 完成模拟数据库查询!
线程 pool-1-thread-1 获取数据集: null
线程 pool-1-thread-1 设置数据集: Data from pool-1-thread-1
线程 pool-1-thread-5 完成模拟数据库查询!
线程 pool-1-thread-5 获取数据集: null
线程 pool-1-thread-5 设置数据集: Data from pool-1-thread-5
线程 pool-1-thread-3 完成模拟数据库查询!
线程 pool-1-thread-3 获取数据集: null
线程 pool-1-thread-3 设置数据集: Data from pool-1-thread-3
线程 pool-1-thread-2 归还连接: Connection-1
线程 pool-1-thread-4 归还连接: Connection-1
线程 pool-1-thread-1 归还连接: Connection-1
线程 pool-1-thread-5 归还连接: Connection-1
线程 pool-1-thread-3 归还连接: Connection-1


以下是运行结果的一部分:

线程 pool-1-thread-2 使用连接进行查询: Connection-1
线程 pool-1-thread-2 开始模拟数据库查询...
线程 pool-1-thread-4 使用连接进行查询: Connection-1
线程 pool-1-thread-4 开始模拟数据库查询...
线程 pool-1-thread-1 使用连接进行查询: Connection-1
线程 pool-1-thread-1 开始模拟数据库查询...
线程 pool-1-thread-5 使用连接进行查询: Connection-1
线程 pool-1-thread-5 开始模拟数据库查询...
线程 pool-1-thread-3 使用连接进行查询: Connection-1
线程 pool-1-thread-3 开始模拟数据库查询...
线程 pool-1-thread-2 完成模拟数据库查询!
线程 pool-1-thread-2 获取数据集: null
线程 pool-1-thread-2 设置数据集: Data from pool-1-thread-2
线程 pool-1-thread-4 完成模拟数据库查询!
线程 pool-1-thread-4 获取数据集: null
线程 pool-1-thread-4 设置数据集: Data from pool-1-thread-4
线程 pool-1-thread-1 完成模拟数据库查询!
线程 pool-1-thread-1 获取数据集: null
线程 pool-1-thread-1 设置数据集: Data from pool-1-thread-1
线程 pool-1-thread-5 完成模拟数据库查询!
线程 pool-1-thread-5 获取数据集: null
线程 pool-1-thread-5 设置数据集: Data from pool-1-thread-5
线程 pool-1-thread-3 完成模拟数据库查询!
线程 pool-1-thread-3 获取数据集: null
线程 pool-1-thread-3 设置数据集: Data from pool-1-thread-3
线程 pool-1-thread-2 归还连接: Connection-1
线程 pool-1-thread-4 归还连接: Connection-1
线程 pool-1-thread-1 归还连接: Connection-1
线程 pool-1-thread-5 归还连接: Connection-1
线程 pool-1-thread-3 归还连接: Connection-1


运行结果解释:


  1. 每个线程从连接池获取连接后,开始模拟数据库查询。查询完成后,归还连接。
  2. 在查询过程中,每个线程都尝试获取、设置和输出公共数据集。每次获取的数据集都是 null,因为每个线程都是第一次访问数据集,所以获取到的值为 null。
  3. 在设置数据集时,每个线程都会设置自己的数据集,例如线程 pool-1-thread-2 设置了数据集为 Data from pool-1-thread-2。
  4. 这些操作表明,每个线程对数据集的操作互不影响,每个线程都有自己的数据集副本,确保了线程间数据的隔离和安全性。


查看ThreadLocal源码

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadLocal<T> {
    // 使用 AtomicInteger 生成哈希码,确保线程安全
    private static final AtomicInteger nextHashCode = new AtomicInteger();
    // 线程局部变量的唯一标识,确保每个 ThreadLocal 实例的唯一性
    private final int threadLocalHashCode = nextHashCode();
    
    // 生成下一个哈希码
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(0x61c88647);
    }

    // 初始化方法,为每个线程的局部变量提供初始值
    protected T initialValue() {
        return null;
    }

    // 获取当前线程的局部变量值
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    // 设置当前线程的局部变量值
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

    // 创建线程的局部变量值映射
    private void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    // 获取当前线程的局部变量值映射
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    // 为当前线程设置初始值
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
        return value;
    }

    // ThreadLocalMap 类
    static class ThreadLocalMap {
        static class Entry {
            final ThreadLocal<?> key;
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                key = k;
                value = v;
            }
        }

        private Entry[] table;

        // 构造函数
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[16];
            int index = firstKey.threadLocalHashCode & (table.length - 1);
            table[index] = new Entry(firstKey, firstValue);
        }

        // 根据 ThreadLocal 获取 Entry
        Entry getEntry(ThreadLocal<?> key) {
            int index = key.threadLocalHashCode & (table.length - 1);
            return table[index];
        }

        // 设置 Entry
        void set(ThreadLocal<?> key, Object value) {
            int index = key.threadLocalHashCode & (table.length - 1);
            table[index] = new Entry(key, value);
        }
    }
}


上面的代码是对 ThreadLocal 类的简化版本的实现,它展示了 ThreadLocal 类的核心部分。下面是对代码的解释:


  • ThreadLocal 类中的 nextHashCode 和 threadLocalHashCode 用于生成哈希码,以及为每个 ThreadLocal 实例提供唯一的标识。
  • get() 方法用于获取当前线程的局部变量值,首先获取当前线程的 ThreadLocalMap,然后通过 ThreadLocal 实例在 ThreadLocalMap 中获取对应的值。
  • set() 方法用于设置当前线程的局部变量值,如果当前线程的 ThreadLocalMap 存在,则直接设置值,否则创建新的 ThreadLocalMap。
  • ThreadLocalMap 类用于存储线程的局部变量值,它内部维护了一个 Entry 数组,通过哈希算法进行快速访问。
  • Entry 类表示 ThreadLocal 实例与局部变量值之间的映射关系。


下面是一个简化版本的 ThreadLocalMap 的代码示例,它展示了 ThreadLocalMap 的基本原理和功能:

public class ThreadLocalMap {

    // Entry 类表示 ThreadLocal 实例与局部变量值之间的映射关系
    static class Entry {
        final ThreadLocal<?> key; // ThreadLocal 实例
        Object value; // 局部变量值

        Entry(ThreadLocal<?> k, Object v) {
            key = k;
            value = v;
        }
    }

    private Entry[] table; // Entry 数组

    // 构造函数,初始化 Entry 数组
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[16];
        int index = firstKey.threadLocalHashCode & (table.length - 1);
        table[index] = new Entry(firstKey, firstValue);
    }

    // 根据 ThreadLocal 获取对应的 Entry
    Entry getEntry(ThreadLocal<?> key) {
        int index = key.threadLocalHashCode & (table.length - 1);
        return table[index];
    }

    // 设置 ThreadLocal 对应的局部变量值
    void set(ThreadLocal<?> key, Object value) {
        int index = key.threadLocalHashCode & (table.length - 1);
        table[index] = new Entry(key, value);
    }
}

下面是对代码的解释:


  • ThreadLocalMap 内部包含一个静态内部类 Entry,用于表示 ThreadLocal 实例与局部变量值之间的映射关系。
  • ThreadLocalMap 内部维护了一个 Entry 数组 table,用于存储 ThreadLocal 实例与局部变量值之间的映射关系。
  • 在 ThreadLocalMap 的构造函数中,通过给定的 ThreadLocal 实例和局部变量值,初始化 table 数组。
  • getEntry(ThreadLocal key) 方法根据给定的 ThreadLocal 实例获取对应的 Entry。
  • set(ThreadLocal key, Object value) 方法用于设置 ThreadLocal 实例对应的局部变量值。
  • ThreadLocalMap 的实现采用了简单的哈希表结构,它通过 ThreadLocal 实例的哈希码来定位存储位置,从而实现了快速访问。每个 Entry 对象包含了 ThreadLocal 实例和对应的局部变量值,通过数组索引来访问和设置。


ThreadLocal的get和put实现


ThreadLocal的get和put方法实现了线程本地变量的存取,它们是通过每个线程内部维护的一个Map来实现的。


get方法实现

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

put方法实现

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}


案例与代码示例


线程上下文管理器案例


线程上下文管理器案例是一个示例,用于展示如何利用 Java 中的 ThreadLocal 实现线程间的数据隔离。在多线程编程中,有时候我们需要在不同的线程中共享数据,但又希望这些数据在每个线程中是独立的,互不影响的。线程上下文管理器通过 ThreadLocal 实现了这种需求。


具体来说,线程上下文管理器案例中的 ThreadContextManager 类提供了 setUserName 和 getUserName 方法,用于在当前线程中设置和获取用户名。每个线程都有自己的 ThreadLocal 实例,因此对于每个线程来说,调用 setUserName 方法设置的用户名都是独立的,互不影响的。


这种机制对于需要在线程间传递上下文信息、保存用户会话信息等场景非常有用。例如,在 Web 开发中,可以利用线程上下文管理器在不同的线程中传递用户的身份信息,而不必通过参数的方式传递,从而简化了代码的编写和维护。

// ThreadContextManager.java
public class ThreadContextManager {
    // 使用ThreadLocal存储用户名
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    // 设置当前线程的用户名
    public static void setUserName(String userName) {
        threadLocal.set(userName);
    }

    // 获取当前线程的用户名
    public static String getUserName() {
        return threadLocal.get();
    }

    public static void main(String[] args) {
        // 创建两个线程,并分别设置不同的用户名
        Thread thread1 = new Thread(() -> {
            ThreadContextManager.setUserName("User1");
            System.out.println("Thread1: User name is " + ThreadContextManager.getUserName());
        });

        Thread thread2 = new Thread(() -> {
            ThreadContextManager.setUserName("User2");
            System.out.println("Thread2: User name is " + ThreadContextManager.getUserName());
        });

        // 启动线程
        thread1.start();
        thread2.start();

        // 等待线程执行结束
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 在主线程中获取用户名
        System.out.println("Main thread: User name is " + ThreadContextManager.getUserName());

        // 在主线程中设置新的用户名
        ThreadContextManager.setUserName("NewUser");

        // 再次获取用户名,确保设置成功
        System.out.println("Main thread: User name is " + ThreadContextManager.getUserName());
    }
}

  • 这个运行结果可以说明什么吗?
Thread1: User name is User1
Thread2: User name is User2
Main thread: User name is null
Main thread: User name is NewUser


这个运行结果说明了以下几点:


  1. 在两个线程中,分别设置了不同的用户名:
  • 线程1设置的用户名为 “User1”;
  • 线程2设置的用户名为 “User2”。


  1. 在主线程中,因为没有设置过用户名,所以调用 getUserName 方法返回的是 null。


  1. 在主线程中,首先设置了新的用户名为 “NewUser”,然后再次调用 getUserName 方法,返回的是 “NewUser”。


这个运行结果说明了每个线程都有自己独立的用户名副本,互不影响。在每个线程中,通过 ThreadContextManager 设置的用户名只在当前线程内可见,不会被其他线程看到,从而确保了线程间数据的隔离性和安全性。


数据库连接池案例


在数据库连接池部分使用 ThreadLocal,其主要作用是为每个线程提供独立的数据库连接,从而保证线程间的数据隔离和线程安全性。


具体来说,数据库连接池的设计通常是为了复用数据库连接,避免频繁地创建和销毁连接,提高系统性能。而使用 ThreadLocal 可以保证每个线程都拥有自己的数据库连接副本,不同线程之间相互独立,互不影响。


在具体实现中,数据库连接池管理类会在每个线程中维护一个数据库连接的副本。当线程需要访问数据库时,通过 ThreadLocal 获取线程对应的数据库连接副本,进行数据库操作;操作完成后,再将连接释放回连接池,线程本地的数据库连接对象也随之被清理,确保了资源的安全释放和线程间的数据隔离。


如果不使用 ThreadLocal,而是直接在数据库连接池中管理共享的连接对象,可能会导致以下问题:


  1. 线程安全性问题: 如果多个线程共享相同的数据库连接对象,那么在多线程环境下可能会出现竞态条件和线程安全性问题。多个线程同时对同一个连接进行操作可能导致连接状态不一致或数据错乱。
  2. 连接泄漏问题: 如果不使用 ThreadLocal 来管理连接,而是将连接对象作为共享资源,那么在多线程环境下很容易出现连接泄漏问题。一个线程使用完连接后没有及时释放,其他线程无法获取到连接,导致连接池资源耗尽。
  3. 连接复用困难: 如果不使用连接池中的连接对象,而是每个线程都创建自己的连接,那么连接的复用就会变得困难。频繁地创建和销毁连接会增加系统开销,并且可能导致数据库性能下降。
  4. 性能下降: 每次请求都需要创建新的连接或者在连接池中竞争连接资源,会增加系统开销和响应时间,导致性能下降。
public class DatabaseConnectionManager {
    private static final ThreadLocal<Connection> connectionHolder = 
                            ThreadLocal.withInitial(() -> createConnection());

    private static Connection createConnection() {
        // 实现创建数据库连接的逻辑
    }

    public static Connection getConnection() {
        return connectionHolder.get();
    }
}

ThreadLocal的应用场景


ThreadLocal通常用于需要在线程之间传递数据,但不希望通过方法参数传递的场景,如Web应用中的会话管理、数据库连接管理等。


Web应用中的会话管理


在一个基于Web的应用程序中,会话管理是非常重要的,因为它允许我们在用户发出多个HTTP请求时跟踪用户的状态和数据。在传统的基于Servlet的Web应用中,通常会使用 HttpSession 来管理用户的会话信息。然而,如果我们在处理请求的过程中没有适当地管理会话信息,会带来一些潜在的问题:


  1. 线程安全性问题: 在多线程环境下,如果多个线程共享同一个 HttpSession 实例,可能会导致线程安全性问题。例如,在一个请求修改了 HttpSession 中的数据,而同时另一个请求也在修改相同的数据,就会导致数据不一致或者丢失的情况。
  2. 数据泄露问题: 如果在处理请求的过程中没有适当地清理会话信息,可能会导致数据泄露的问题。例如,如果一个用户的会话信息被错误地共享给了另一个用户,那么可能会泄露敏感信息。
  3. 并发访问问题: 如果没有合适的机制来处理多个线程同时访问同一个 HttpSession 实例的情况,可能会导致并发访问问题,例如临界资源竞争或者死锁。


为了解决上述问题,可以使用 ThreadLocal 来管理每个线程的会话信息。ThreadLocal 可以确保每个线程都拥有自己的会话信息副本,互不干扰,从而避免了多线程并发访问的问题。

/**
 * 在一个基于Web的应用程序中,每个HTTP请求都是一个独立的线程。
 * 为了在请求处理过程中共享会话信息,可以使用ThreadLocal来存储当前用户的会话信息。
 */
public class SessionManager {
    // 使用ThreadLocal存储当前线程的用户会话信息
    private static final ThreadLocal<UserSession> userSessionThreadLocal = new ThreadLocal<>();

    /**
     * 设置当前线程的用户会话信息。
     *
     * @param userSession 当前线程的用户会话信息
     */
    public static void setUserSession(UserSession userSession) {
        userSessionThreadLocal.set(userSession);
    }

    /**
     * 获取当前线程的用户会话信息。
     *
     * @return 当前线程的用户会话信息
     */
    public static UserSession getUserSession() {
        return userSessionThreadLocal.get();
    }
}

如果没有 ThreadLocal,而直接在多线程环境中共享 HttpSession 实例,则可能会导致上述提到的问题。因为每个线程都可以同时访问和修改同一个 HttpSession 实例,可能会造成数据的不一致性、泄露和并发访问问题。因此,使用 ThreadLocal 可以有效地解决这些问题,确保每个线程都能够独立地管理自己的会话信息,从而提高了系统的健壮性和安全性。


相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
69 2
|
4月前
|
JavaScript 前端开发
【Vue面试题二十五】、你了解axios的原理吗?有看过它的源码吗?
这篇文章主要讨论了axios的使用、原理以及源码分析。 文章中首先回顾了axios的基本用法,包括发送请求、请求拦截器和响应拦截器的使用,以及如何取消请求。接着,作者实现了一个简易版的axios,包括构造函数、请求方法、拦截器的实现等。最后,文章对axios的源码进行了分析,包括目录结构、核心文件axios.js的内容,以及axios实例化过程中的配置合并、拦截器的使用等。
【Vue面试题二十五】、你了解axios的原理吗?有看过它的源码吗?
|
14天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
32 2
|
21天前
|
NoSQL 中间件 Java
字节面试:聊聊 CAP 定理?哪些中间件是AP? 哪些是CP? 说说 为什么?
45岁老架构师尼恩在其读者交流群中分享了关于CAP定理的重要面试题及其解析,包括CAP定理的基本概念、CAP三要素之间的关系,以及如何在分布式系统设计中权衡一致性和可用性。文章还详细分析了几种常见中间件(如Redis Cluster、Zookeeper、MongoDB、Cassandra、Eureka、Nacos)的CAP特性,并提供了高端面试技巧,帮助读者在面试中脱颖而出。尼恩还推荐了其团队编写的《尼恩Java面试宝典PDF》等资料,助力求职者准备面试,提升技术水平。
|
4月前
|
JavaScript 前端开发
【Vue面试题二十七】、你了解axios的原理吗?有看过它的源码吗?
文章讨论了Vue项目目录结构的设计原则和实践,强调了项目结构清晰的重要性,提出了包括语义一致性、单一入口/出口、就近原则、公共文件的绝对路径引用等原则,并展示了单页面和多页面Vue项目的目录结构示例。
|
2月前
|
Arthas Kubernetes Java
字节面试:CPU被打满了,CPU100%,如何处理?
尼恩,一位拥有20多年经验的老架构师,针对近期读者在一线互联网企业面试中遇到的CPU 100%和红包架构等问题,进行了系统化梳理。文章详细解析了CPU 100%的三大类型问题(业务类、并发类、内存类)及其九种常见场景,提供了使用jstack和arthas两大工具定位问题的具体步骤,并分享了解决死锁问题的实战案例。尼恩还强调了面试时应先考虑回滚版本,再使用工具定位问题的重要性。此外,尼恩提供了丰富的技术资料,如《尼恩Java面试宝典》等,帮助读者提升技术水平,轻松应对面试挑战。
字节面试:CPU被打满了,CPU100%,如何处理?
|
1月前
|
存储 缓存 网络协议
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点,GET、POST的区别,Cookie与Session
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点、状态码、报文格式,GET、POST的区别,DNS的解析过程、数字证书、Cookie与Session,对称加密和非对称加密
|
3月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
477 37
|
2月前
|
Java API 对象存储
JVM进阶调优系列(2)字节面试:JVM内存区域怎么划分,分别有什么用?
本文详细解析了JVM类加载过程的关键步骤,包括加载验证、准备、解析和初始化等阶段,并介绍了元数据区、程序计数器、虚拟机栈、堆内存及本地方法栈的作用。通过本文,读者可以深入了解JVM的工作原理,理解类加载器的类型及其机制,并掌握类加载过程中各阶段的具体操作。
|
3月前
|
安全 Java 数据库连接
反问面试官3个ThreadLocal的问题
接下来,我想先说说ThreadLocal的用法和使用场景,然后反问面试官3个关于ThreadLocal的话题。
反问面试官3个ThreadLocal的问题