这里从多线程的角度来进行切入!

  1. 首先在我的技术派项目当中,首页采用的多线程来并行加载不同的模块,这里强调的是多个线程并行的执行!那你有没有思考过如果多线程之间交替执行会怎么样呢?顺带的引出了下面的内容。

  2. 需要回答的一个问题,线程之间要进行切换,那你了解线程的上下文切换吗?说说机制

  3. 既然你提到了线程之间的切换,那他们之间会不会有通信呢?请你了解都有哪些通信的方式?

  4. 接着是进一步的深入不同的通信机制之间的差别,主要集中在不同的关键字的使用上了

你平时有用过多线程吗?你在代码中是哪些场景用呢?

用得比较多,比如说在技术派的首页内容加载中,就用到了多线程来并行加载不同的模块,提高页面的响应速度。

那你既然聊到了多线程,那你了不了解多线程相互之间是怎么进行切换的呢?线程上下文切换了解吗

线程的上下文切换是指CPU从一个线程切换到另外一个线程的过程。

在线程切换的过程中,需要保存当前线程的状态,加载下一个线程的上下文

之所以要这样,是因为 CPU 在同一时刻只能执行一个线程,为了实现多线程并发执行,需要不断地在多个线程之间切换。【这应该要强调是单核CPU】

为了让用户感觉多个线程是在同时执行的, CPU 资源的分配采用了时间片轮转的方式,线程在时间片内占用 CPU 执行任务。当线程使用完时间片后,就会让出 CPU 让其他线程占用。

那你既然提到了线程之间的上下文切换,那你了解守护线程吗

Java 中的线程分为两类,一种是守护线程,另外一种是用户线程。

JVM 启动时会调用 main 方法,main 方法所在的线程就是一个用户线程。

守护线程是一种专门为其他线程来服务的线程,当主线程结束的时候,无论守护线程的任务是否执行完都会结束。像垃圾回收线程就是一种守护线程,当主线程执行完,那么垃圾回收线程也会结束

只要有一个用户线程还没结束,正常情况下 JVM 就不会退出。

那既然你知道了线程之间是会相互沟通的,如果不沟通,都是你干你的,我干我的,肯定没有保证线程的安全性,那么请你讲讲,你都知道线程之间有哪些通信的方式

这个部分没有第一时间想到哪些通信的方式,需要补一下:

  1. 首先肯定就是volatile和synchronized这两个关键字!

  2. wait-notify生产者-消费者模式,exchanger数据的交换【补】,condition应该是针对的是可以创建多个小房间的那个锁!

线程之间传递信息的方式有多种,比如说使用 volatile 和 synchronized 关键字共享对象、使用 wait()notify() 方法实现生产者-消费者模式、使用 Exchanger 进行数据交换、使用 Condition 实现线程间的协调等。

那你都知道这么多种通信的方式了,那么请你先来讲讲 volatile 和 synchronized 关键字共享对象 的通信方式

volatile保证的变量的可见性,可以用来修饰成员变量,告知程序任何对该变量的访问均需要从共享内存中获取,并同步刷新回共享内存,保证所有线程对变量访问的可见性。

synchronized 可以修饰方法,或者同步代码块,确保多个线程在同一个时刻只有一个线程在执行方法或代码块。

多个线程可以通过 volatile 和 synchronized 关键字访问和修改同一个对象,从而实现信息的传递

emmmmm,你再讲讲wait-notify生产者-消费者模式的通信方式

wait()会让当前正在执行的线程释放锁,并进入等待池当中进行等待,直到被其他线程唤醒

notify()主要的作用就是唤醒等待池当中的线程,如果没有在括号里面进行声明,那么就是随机唤醒等待池当中的某一个线程;如果声明指定了,那就指定唤醒对应的线程

额外讲一讲notifyall(),这个是唤醒全部的线程!

condition你了解吗?具体会和哪个锁一起发挥作用呢

condition的话通常与锁 ReentrantLock 一起使用,为线程提供了一种等待某个条件成真的机制,并允许其他线程在该条件变化时通知等待线程。

相当于是提供了很多个小房间,如果条件成立,就可以进入到对应的房间中!

类比一下前面的wait()方法,Condition 也提供了类似的方法,await() 负责阻塞、signal()signalAll() 负责通知。但是和wait()不同的是,condition可以根据条件来分配不同的锁,让不同的线程根据条件进入到不同的房间当中!

exchange的通信方式你了解吗?

补充:对于exchange的方式已经遗忘,需要补充!

Exchanger 是一个同步点,可以在两个线程之间交换数据。一个线程调用 exchange() 方法,将数据传递给另一个线程,同时接收另一个线程的数据。

CompletableFuture 的使用方式了解吗?

CompletableFuture 是 Java 8 引入的一个类,支持异步编程,允许线程在完成计算后将结果传递给其他线程。

聊了那么多,请你说说如何保证线程的安全性

线程安全是指在并发环境下,多个线程访问共享资源时,程序能够正确地执行,而不会出现数据不一致的问题。

为了保证线程安全,可以使用 synchronized 关键字对方法加锁,对代码块加锁。线程在执行同步方法、同步代码块时,会获取类锁或者对象锁,其他线程就会阻塞并等待锁。

如果需要更细粒度的锁,可以使用 ReentrantLock 并发重入锁等。

如果需要保证变量的内存可见性,可以使用 volatile 关键字

对于简单的原子变量操作,还可以使用 Atomic 原子类

对于线程独立的数据,可以使用 ThreadLocal 来为每个线程提供专属的变量副本。

对于需要并发容器的地方,可以使用 ConcurrentHashMapCopyOnWriteArrayList 等。

说一个线程安全的使用场景?

单例模式。在多线程环境下,如果多个线程同时尝试创建实例,单例类必须确保只创建一个实例,并提供一个全局访问点。

饿汉式是一种比较直接的实现方式,它通过在类加载时就立即初始化单例对象来保证线程安全。


class Singleton { private static final Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } }

懒汉式单例则在第一次使用时初始化单例对象,这种方式需要使用双重检查锁定来确保线程安全,volatile 关键字用来保证可见性,syncronized 关键字用来保证同步。


class LazySingleton { private static volatile LazySingleton instance; private LazySingleton() {} public static LazySingleton getInstance() { if (instance == null) { // 第一次检查 synchronized (LazySingleton.class) { if (instance == null) { // 第二次检查 instance = new LazySingleton(); } } } return instance; } }

能说一下 Hashtable 的底层数据结构吗?

注意一下,面试过程中一定要听清楚面试官问的是什么!!!

与 HashMap 类似,Hashtable 的底层数据结构也是一个数组加上链表的方式,然后通过 synchronized 加锁来保证线程安全。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐