一文搞定互联网大厂必问Java线程面试题

简介: 本文全面介绍了Java线程的基础知识,旨在帮助读者深入理解并发编程中的线程概念、创建方法、使用目的和好处,以及线程安全和死锁问题。

前言

并发编程技术是互联网应用开发中必须掌握的知识,从本篇文章开始笔者将从线程知识开始学习,一步一步了解Java领域并发编程知识。以问题的形式来介绍线程相关的知识点。

一、Java线程是什么?

一个运行的程序就是一个进程,一个进程中可以有多个线程(线程是程序执行的最小单元)。 
AI 代码解读

二、定义并开启Java线程的方法

1、继承Thread,重写run方法

//定义线程public class MyThread extends Thread {​    
@Override    
public void run() {
//业务逻辑    
}}
//开启线程
new MyThread().start();
AI 代码解读

2、实现Runnable,重写run方法

class MyRunnable implements Runnable{
   
   ​    
@Override    
public void run() {
   
           
//业务逻辑    
}}
//开启线程
new Thread(new MyRunnable()).start();
AI 代码解读

3、FutureTask 实现Callable接口,重写call方法, 实现有返回结果的线程 通过自旋 get方法通过自旋等待执行完成或者异常

class MyCallableTask extends Callable {    
@Override    public Object call() throws Exception {        
//业务逻辑        return null;    
}}
//提交一个FutureTask对象到线程池执行,并拿到结果

ThreadPoolExecutor executor = new ThreadPoolExecutor(2,5,2, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10));
Future<?> result = executor.submit(new FutureTask(new MyCallableTask()));
AI 代码解读

4、线程池

ThreadPoolExecutor executor = new ThreadPoolExecutor(2,5,2, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10));
//提交一个Runnable任务到线程池
executor.execute(new Runnable() {
   
   
@Override            
public void run() {
   
   
//业务逻辑            
}        
});
AI 代码解读

三、使用线程的目的?

1、多核心cpu的场景下,真正实现并行的计算,

2、提高程序吞吐量,最大化利用硬件的性能能力。

3、异步处理

四、使用线程的好处?

1、在一个应用进程中,会存在多个同时执行的任务,通过对不同任务创建不同的线程去处理,可以提升程序处理的实时性,提高程序吞吐量。

2、充分利用cpu多核心的特征,最大化利用硬件的性能能力

3、通过开启线程异步处理,提升响应速度,提升用户对程序的使用体验。

五、线程使用场景

1、网络通信BIO模型优化

2、文件跑批

3、复杂业务处理

4、中间件开启后台线程清理资源,同步数据等

5、定时任务等

六、Java线程运行流程,状态?

1、在Thread类中,定义了6种状态:

public enum State {
NEW, //新建
RUNNABLE, //运行中
BLOCKED, //同步锁获取阻塞
WAITING, //等待
TIMED_WAITING, //超时等待
TERMINATED; //终止
}
AI 代码解读

各种状态代码演示:

  • Thread.sleep(n)后进入TIMED_WAITING状态

Object.wait()方法,进入WAITING状态

如果是Object.wait(n)方法,进入TIMED_WAITING状态

两个线程都去竞争同一把锁,成功进入睡眠TIMED_WAITING,失败的进入BLOCKING

最终得到如下状态流转图:

这里的问题:

Thread.sleep(0),会干什么?线程立马恢复运行

Object.wait(0)方法和Object.wait()方法效果相同。

七、Java线程如何正确停止呢?

1、Thread.stop,jdk官方提供的强制停止线程的方法,会破坏程序的数据完整性,由于它会释放所有锁住的监视器对象,如果先前由这些监视器保护的任何对象处于不一致状态,则损坏的对象将对其他线程可见,从而可能导致任意行为。

2、可以通过Thread.interrupt()方法来给线程设置一个中断标记,然后在程序中循环判断中断状态是否设置成true。

Thread的interrupt()方法是通过native方法实现的,jvm在linux系统下的实现如下:

3、下面看下停止一个正在执行的线程的正确姿势:

4、如果线程正在sleep要怎么停止呢?

5、验证park方法也会被interrupt方法中断

总结:

推荐使用interrupt方法中断,sleep/park的线程也可以响应中断请求。

八、wait yield sleep notify/notifyall 区别

  • sleep()

  sleep()方法需要指定等待的时间,进入waiting状态,它可以让当前正在执行的线程(这里要注意)在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是sleep()方法不会释放锁标志,也就是说如果有synchronized同步块,其他线程仍然不能访问共享数据。

如果线程被中断了,会响应中断异常,停止睡眠。睡眠使当前正在执行的线程休眠(暂时停止)在规定的时间内,根据系统计时器和调度程序。线程不会失去任何监视器的所有权,恢复执行将取决于时间安排和可用性,执行线程的处理器。

    调用sleep方法前,不会加载写数据缓存从寄存器到共享内存,调用sleep方法后,也不会重新加载寄存器的缓存
AI 代码解读
  • wait()

    wait()方法与sleep()方法的不同之处在于,wait()方法会释放对象的“锁标志” wait方法使线程进入阻塞状态,blocking.

wait(),notify()及notifyAll()只能在synchronized语句中使用,但是如果使用的是ReentrantLock实现同步,该如何达到这三个方法的效果呢?解决方法是使用ReentrantLock.newCondition()获取一个Condition类对象,然后Condition的await(),signal()以及signalAll()分别对应上面的三个方法。

调用wait方法(释放同步锁),会使得当前线程进入到当前对象的等待池wait set中,并且放弃当前对象的同步锁资源,进入等待monitor状态,直到发生如下情况:

1、其他线程调用notify并且被选中为被唤醒的线程

2、其他线程调用notifyAll方法

3、一些线程中断当前线程,Thread#interrupt()

4、指定的时间到了

  • notify()

    随机唤醒一个正在等待该对象上的monitor的线程

  • notifyAll()

    唤醒所有正在等待该对象上的monitor的线程,重新抢占锁。

  • yield()

    放弃cpu资源,不会释放对象锁

  • join()

    join()方法会使当前线程等待调用join()方法的线程结束后才能继续执行,实际调用了wait方法.

    join方法源码如下:

// 同步代码块 获取线程对象的对象锁//如果在main线程中调用,t.join main线程获取到对象t的锁public final synchronized void join(long millis)throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) 
{
//"testCallJoinThread" #1 prio=5 os_prio=31 tid=0x00007fda23008800 nid=0xf03 //in Object.wait() [0x0000700004b53000]
   java.lang.Thread.State: WAITING (on object monitor)
//main线程进入WAITING状态
//等当前线程t1执行完成,jvm会执行完的时候,调用当前线程t1的notifyAll方法唤醒等待当前线程t1的线程main线程。
wait(0);
}} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}}}
AI 代码解读

九、线程安全是什么

  • 原子性

  • 可见性

  • 有序性

十、什么是线程死锁,活锁

  • 死锁:

    一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象。

  • 活锁:

    活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。处于活锁的实体是在不断的改变状态,活锁有可能自行解开

**十一、死锁发生的条件**
AI 代码解读

1、这四个条件同时满足,就会产生死锁。

  • 互斥,共享资源 X 和 Y 只能被一个线程占用;
  • 占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;
  • 不可抢占,其他线程不能强行抢占线程 T1 占有的资源;
  • 循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。

2、如何解决死锁问题

按照前面说的四个死锁的发生条件,我们只需要破坏其中一个,就可以避免死锁的产生。

其中,互斥这个条件我们没有办法破坏,因为我们用锁为的就是互斥,其他三个条件都有办法可以破坏

  • 对于“占用且等待”这个条件,我们可以一次性申请所有的资源,这样就不存在等待了。

  • 对于“不可抢占”这个条件,占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源,这样不可抢占这个条件就破坏掉了。

  • 对于“循环等待”这个条件,可以靠按序申请资源来预防。所谓按序申请,是指资源是有线性顺序的,申请的时候可以先申请资源序号小的,再申请资源序号大的,这样线性化后自然就不存在循环了。按顺序申请资源。

死锁演示

public static void main(String[] args) {
   
    
Object o = new Object();        
Object o2 = new Object();​        
new Thread(new Runnable(){
   
   ​            
@Override            
public void run() {
   
                   
synchronized (o) {
   
                       
try {
   
                           
Thread.sleep(2000);                    
} catch (InterruptedException e) {
   
   
e.printStackTrace();                   
}                    
synchronized (o2) {
   
    
System.out.println("1 locked "); 
}                }            }        
}).start();​        
new Thread(new Runnable(){
   
   ​            
@Override            
public void run() {
   
                   
synchronized (o2) {
   
   
try {
   
                           
Thread.sleep(2000);                    
} catch (InterruptedException e) {
   
   
e.printStackTrace();                    
}                    
synchronized (o) {
   
     
System.out.println("2 locked ");    
}                
}           
}        
}).start();    }
AI 代码解读

线程知识就介绍这么多了,通过代码实践发现很多知识还是很有意思的,欢迎大家补充。

目录
打赏
0
1
1
0
41
分享
相关文章
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
Java社招面试中的高频考点:Callable、Future与FutureTask详解
大家好,我是小米。本文主要讲解Java多线程编程中的三个重要概念:Callable、Future和FutureTask。它们在实际开发中帮助我们更灵活、高效地处理多线程任务,尤其适合社招面试场景。通过 Callable 可以定义有返回值且可能抛出异常的任务;Future 用于获取任务结果并提供取消和检查状态的功能;FutureTask 则结合了两者的优势,既可执行任务又可获取结果。掌握这些知识不仅能提升你的编程能力,还能让你在面试中脱颖而出。文中结合实例详细介绍了这三个概念的使用方法及其区别与联系。希望对大家有所帮助!
202 60
面试大神教你:如何巧妙回答线程优先级这个经典考题?
大家好,我是小米。本文通过故事讲解Java面试中常见的线程优先级问题。小明和小华的故事帮助理解线程优先级:高优先级线程更可能被调度执行,但并非越高越好。实际开发需权衡业务需求,合理设置优先级。掌握线程优先级不仅能写出高效代码,还能在面试中脱颖而出。最后,小张因深入分析成功拿下Offer。希望这篇文章能助你在面试中游刃有余!
45 4
面试大神教你:如何巧妙回答线程优先级这个经典考题?
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
110 14
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
57 13
面试中的难题:线程异步执行后如何共享数据?
本文通过一个面试故事,详细讲解了Java中线程内部开启异步操作后如何安全地共享数据。介绍了异步操作的基本概念及常见实现方式(如CompletableFuture、ExecutorService),并重点探讨了volatile关键字、CountDownLatch和CompletableFuture等工具在线程间数据共享中的应用,帮助读者理解线程安全和内存可见性问题。通过这些方法,可以有效解决多线程环境下的数据共享挑战,提升编程效率和代码健壮性。
68 6
Java线程调度揭秘:从算法到策略,让你面试稳赢!
在社招面试中,关于线程调度和同步的相关问题常常让人感到棘手。今天,我们将深入解析Java中的线程调度算法、调度策略,探讨线程调度器、时间分片的工作原理,并带你了解常见的线程同步方法。让我们一起破解这些面试难题,提升你的Java并发编程技能!
103 16
Java 高级面试技巧:yield() 与 sleep() 方法的使用场景和区别
本文详细解析了 Java 中 `Thread` 类的 `yield()` 和 `sleep()` 方法,解释了它们的作用、区别及为什么是静态方法。`yield()` 让当前线程释放 CPU 时间片,给其他同等优先级线程运行机会,但不保证暂停;`sleep()` 则让线程进入休眠状态,指定时间后继续执行。两者都是静态方法,因为它们影响线程调度机制而非单一线程行为。这些知识点在面试中常被提及,掌握它们有助于更好地应对多线程编程问题。
89 9
Java面试必问!run() 和 start() 方法到底有啥区别?
在多线程编程中,run和 start方法常常让开发者感到困惑。为什么调用 start 才能启动线程,而直接调用 run只是普通方法调用?这篇文章将通过一个简单的例子,详细解析这两者的区别,帮助你在面试中脱颖而出,理解多线程背后的机制和原理。
78 12
面试直击:并发编程三要素+线程安全全攻略!
并发编程三要素为原子性、可见性和有序性,确保多线程操作的一致性和安全性。Java 中通过 `synchronized`、`Lock`、`volatile`、原子类和线程安全集合等机制保障线程安全。掌握这些概念和工具,能有效解决并发问题,编写高效稳定的多线程程序。
110 11

热门文章

最新文章

目录
目录
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等