一文吃透面试线程必问10大问题

简介: 本文全面探讨了Java线程的十个关键面试问题,涵盖了线程的基本概念、创建方法、使用目的与好处、运行流程与状态、停止线程的正确方式、以及线程安全等高级主题。

一、Java线程是什么?

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

二、开启一个Java线程的方法

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

2、继承Thread,重写run方法

3、实现Runnable,重写run方法

4、线程池ThreadPoolExecutor

三、使用线程的目的?

1、多核心cpu的场景下,真正实现并行的计算, 2、提高程序吞吐量,最大化利用硬件的性能能力。 3、异步处理

四、使用线程的好处?

1、在一个应用进程中,会存在多个同时执行的任务,通过对不同任务创建不同的线程去处理,可以提升程序处理的实时性,提高程序吞吐量。 2、充分利用cpu多核心的特征,最大化利用硬件的性能能力 3、通过开启线程异步处理,提升相应速度,提升用户对程序的使用体验。

五、线程使用场景

BIO模型优化(BIO一个连接需要一个线程,线程开销大),文件跑批,复杂业务处理,中间件开启后台线程清理资源,定时任务等

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

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

public enum State {
   
   
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }

各种状态代码演示:

Thread.sleep(n)的状态,TIMED_WAITING状态

image.png

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

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

image.png

最后把状态图流转过程画一下

image.png

这里的问题: Thread.sleep(0),会干什么?线程立马恢复运行 Object.wait(0)方法和Object.wait(n)方法效果相同。

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

1、Thread.stop,jdk官方提供的强制停止线程的方法,会破坏程序的数据完整性,由于它会释放所有锁住的监视器对象,如果先前由这些监视器保护的任何对象处于不一致状态,则损坏的对象将对其他线程可见,从而可能导致任意行为。 2、可以通过Thread.interrupt()方法来给线程设置一个中断标记,然后在程序中循环判断中断状态是否设置成true。

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

image.png

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

image.png

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

image.png

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

image.png

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

八、wait yield sleep notify/notifyall 区别

sleep()

  sleep()方法需要指定等待的时间,进入waiting状态,它可以让当前正在执行的线程(这里要注意)在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是sleep()方法不会释放锁标志,也就是说如果有synchronized同步块,其他线程仍然不能访问共享数据。 如果线程被中断了,会响应中断异常,停止睡眠。睡眠使当前正在执行的线程休眠(暂时停止)在规定的时间内,根据系统计时器和调度程序。线程不会失去任何监视器的所有权,恢复执行将取决于时间安排和可用性,执行线程的处理器。

调用sleep方法前,不会加载写数据缓存从寄存器到共享内存,调用sleep方法后,也不会重新加载寄存器的缓存

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方法. 讲Happens-Before可见性模型的时候讲过,它的作用其实就是让线程的执 行结果对后续线程的访问可见?

// 同步代码块 获取线程对象的对象锁
//如果在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()) {
   
   
            //main线程将阻塞,进入blocking状态,
            //等当前线程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;
            }
        }
    }
如果想让多个线程按顺序执行?就可以使用join方法。

1、使用t1.join方法 当前线程会等待t1执行完毕

原理:

首先join() 是一个synchronized方法, 里面调用了wait(),这个过程的目的是让持有这个同步锁的线程进入等待,那么谁持有了这个同步锁呢?答案是主线程,因为主线程调用了t1.join()方法,相当于在t1.join()代码这块写了一个同步代码块,谁去执行了这段代码呢,是主线程,所以主线程被wait()了。然后在子线程t1执行完毕之后,JVM会调用lock.notify_all(thread);唤醒持有threadA这个对象锁的线程,也就是主线程,会继续执行。

2、通过ExecutorService.newSingleThreadExecutor();

在分别提交多个任务,使用创建单个线程的线程池,他会将提交的线程任务放入一个FIFO队,一个一个执行。

yield方法

Java线程中的Thread.yield( )方法,译为线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行,注意是让自己或者其他线程运行,并不是单纯的让给其他线程。 yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!

九、线程安全是什么

原子性

可见性

有序性

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

死锁:

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

活锁:

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

死锁发生的条件

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

互斥,共享资源 X 和 Y 只能被一个线程占用;

占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;

不可抢占,其他线程不能强行抢占线程 T1 占有的资源;

循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。

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

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

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

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

对于“循环等待”这个条件,可以靠按序申请资源来预防。所谓按序申请,是指资源是有线性顺序的,申请的时候可以先申请资源序号小的,再申请资源序号大的,这样线性化后自然就不存在循环了。
相关文章
|
4月前
|
存储 安全 Java
【Java集合类面试二十五】、有哪些线程安全的List?
线程安全的List包括Vector、Collections.SynchronizedList和CopyOnWriteArrayList,其中CopyOnWriteArrayList通过复制底层数组实现写操作,提供了最优的线程安全性能。
|
4月前
|
安全 Java 数据库
一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。
|
4月前
|
存储 监控 安全
一天十道Java面试题----第三天(对线程安全的理解------>线程池中阻塞队列的作用)
这篇文章是Java面试第三天的笔记,讨论了线程安全、Thread与Runnable的区别、守护线程、ThreadLocal原理及内存泄漏问题、并发并行串行的概念、并发三大特性、线程池的使用原因和解释、线程池处理流程,以及线程池中阻塞队列的作用和设计考虑。
|
11天前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
3月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
3月前
|
消息中间件 前端开发 NoSQL
面试官:线程池遇到未处理的异常会崩溃吗?
面试官:线程池遇到未处理的异常会崩溃吗?
77 3
面试官:线程池遇到未处理的异常会崩溃吗?
|
3月前
|
消息中间件 存储 前端开发
面试官:说说停止线程池的执行流程?
面试官:说说停止线程池的执行流程?
52 2
面试官:说说停止线程池的执行流程?
|
3月前
|
消息中间件 前端开发 NoSQL
面试官:如何实现线程池任务编排?
面试官:如何实现线程池任务编排?
36 1
面试官:如何实现线程池任务编排?
|
4月前
|
安全 Java
【Java集合类面试十三】、HashMap如何实现线程安全?
实现HashMap线程安全的方法包括使用Hashtable类、ConcurrentHashMap,或通过Collections工具类将HashMap包装成线程安全的Map。
【多线程面试题 一】、 创建线程有哪几种方式?
创建线程的三种方式包括继承Thread类、实现Runnable接口和实现Callable接口,其中Runnable和Callable接口方式更受推荐,因为它们允许多重继承并更好地体现面向对象思想。

热门文章

最新文章

下一篇
无影云桌面